Error Types & Causes

The Course
•
5 min read

A caller may need to know which task failed as well as why it failed. Extend Error to introduce a recognizable TaskError carrying an index and the original cause. The second Error constructor argument accepts an options object with cause in the environments used by this course.

Keep messages useful for people and explicit properties useful for code. Do not ask callers to parse an index back out of an English message. instanceof TaskError works for instances from this class in the same realm; serializing an error to JSON or crossing a realm does not preserve every identity assumption.

A thrown value may be undefined, null or text. Store an explicit failed flag instead of testing whether the stored error is truthy. Likewise, preserve the first observed failure rather than overwriting it every time another cancelled callback rejects. This policy is about observation order, not guaranteeing which physical operation failed first.

Read the subclass constructor

class TaskError extends Error makes TaskError a subclass of Error: an instance is also an Error. Inside its constructor, super(message, options) calls the Error constructor to initialize the message and cause. Call it before accessing this; then add the task-specific index and name. Here cause is the original failure value, not necessarily another Error.

Follow the example in three steps: initialize the Error part with super, store the index on this instance, then inspect the instance in the caller. You do not need a general inheritance hierarchy to use this one specialized error. See the extends reference for the language rule.

Predict, run, and explain

Write the output and trace the important state changes before checking it. Then run the program. Each editor is independent. Async examples end with main() so this editor waits for their work.

Predict, then run JavaScript
class TaskError extends Error {
  constructor(index, cause) {
    super(`Task ${index} failed`, { cause });
    this.name = "TaskError";
    this.index = index;
  }
}

const error = new TaskError(2, new Error("offline"));

console.log(error instanceof Error);
console.log(error.index, error.cause.message);
Check your prediction
true
2 offline

Preserve machine-readable failure information

An error message helps a person, while explicit properties let code inspect a failure without parsing English text. Preserve an original cause rather than replacing it with an unrelated message. A task index and cause describe a different failure boundary from an input iterator error.

Not every thrown value is an Error instance. Undefined and null are still legal failures. Track whether failure occurred with a separate flag so a falsy reason cannot accidentally turn rejection into success.

Preserve machine-readable failure information: trace JavaScript
class TaskError extends Error {
  constructor(index, cause) {
    super(`Task ${index} failed`, { cause });
    this.index = index;
  }
}

const original = new Error("offline");
const wrapped = new TaskError(2, original);

console.log(wrapped.index);
console.log(wrapped.cause === original);
console.log(wrapped instanceof Error);
Check your prediction
2
true
true

The original object survives wrapping by identity. Callers can inspect cause and index separately; changing message wording should not break those callers.

Preserve machine-readable failure information: contrast JavaScript
let failed = false;
let reason;

try {
  throw undefined;
} catch (error) {
  failed = true;
  reason = error;
}

console.log(failed);
console.log(Boolean(reason));
Check your prediction
true
false

A truthiness check on reason would misclassify the outcome. This is why the final pool keeps a failed flag independently of its stored failure value.

Try the rule on new inputs

Test an Error, text, null and undefined as causes. For mapper failure versus source failure, state exactly which one is wrapped and which original value the caller should receive.

Your turn

Exercise

Error Types & Causes: practise

Implement TaskError with index and cause. Wrap a string cause “bad input” at index 3. Print TaskError, 3 and bad input. Do not convert the cause into a new unrelated message.

Your workspace JavaScript
// Extend Error and set a stable name for readable diagnostics.
Hint 1: the idea

The cause may be any value, not just an Error instance.

Hint 2: a first step

Pass { cause } to super and save index on the instance.

Show one solution
class TaskError extends Error {
  constructor(index, cause) {
    super(`Task ${index} failed`, { cause });
    this.name = "TaskError";
    this.index = index;
  }
}

const error = new TaskError(3, "bad input");

console.log(error.name);
console.log(error.index);
console.log(error.cause);
TaskError
3
bad input

Explain why the implementation meets the stated contract. Which part is enforced by the language or checker, and which part needs behavioral evidence?

Change it and test it

Test undefined as a cause. Create a separate failed flag and demonstrate that throwing undefined still sets it. Explain why testing if(error) cannot represent every thrown value and why a cleanup error should not erase the primary failure.

Was this article helpful?

Your response is saved on this device.