Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

JavaScript (ES2026)  Module 9 – Errors and debugging

Custom errors, error cause and Error.isError

Create error classes with a name and their own fields, wrap low-level failures with the cause option, and recognise real error objects with Error.isError.

  • Intermediate
  • 19 minutes
  • Examples run with Node.js 24.21.0 and quickjs 0.32.0
  • By MySmartCoPilot
Free lesson, Pro answers: 19 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Create custom error classes that set their own name and carry extra fields
  • Wrap a low-level error with the cause option and print the whole chain of causes
  • Detect real error objects with Error.isError, including errors from another realm
  • Serialize an error, its fields and its causes into JSON for a log

Before you start

What you’ll learn · Before you start

What you will learn

  • Create custom error classes that set their own name and carry extra fields
  • Wrap a low-level error with the cause option and print the whole chain of causes
  • Detect real error objects with Error.isError, including errors from another realm
  • Serialize an error, its fields and its causes into JSON for a log

Before you start

On this page

The built-in classes describe failures of the language, and a program also has failures of its own: a seat that is already booked, a card the bank declined, a request that came back with status 503. Give each of them a custom error class, a class that extends Error, sets its own name and carries the data a caller needs, such as the seat or the status code. Callers can then tell one failure from another with instanceof and read the data instead of parsing a message. When one failure leads to another, the cause option, new Error(message, { cause }) from ECMAScript 2022, keeps the original error attached to the new one, so a log can show the whole chain. And Error.isError(value), new in ECMAScript 2026, tells whether a value really is an error object, which instanceof cannot always do. This lesson builds all three and ends with errors that survive the trip into a log.

A custom error class

A custom error needs only a few lines of class syntax, which a later module of this track covers in full. The class extends Error, passes the message to super, and adds what makes it useful:

An error class for a seat that is already booked JavaScript · custom-class.mjs
// A custom error: a class that extends Error, sets its name and carries the data callers need.
class SeatTakenError extends Error {
  constructor(seat, options) {
    super(`seat ${seat} is already booked`, options);
    this.name = 'SeatTakenError';
    this.seat = seat;
  }
}

function book(seat, taken) {
  if (taken.has(seat)) throw new SeatTakenError(seat);
  taken.add(seat);
  return `booked ${seat}`;
}

const taken = new Set(['12A']);
for (const seat of ['14C', '12A']) {
  try {
    console.log(book(seat, taken));
  } catch (error) {
    if (!(error instanceof SeatTakenError)) throw error;
    console.log(`${error.name}: ${error.message} (seat field: ${error.seat})`);
    console.log(`instanceof Error: ${error instanceof Error}, String(): ${String(error)}`);
  }
}

class NoName extends Error {}
console.log(`without a name of its own: ${String(new NoName('seat 12A is already booked'))}`);

Output

booked 14C
SeatTakenError: seat 12A is already booked (seat field: 12A)
instanceof Error: true, String(): SeatTakenError: seat 12A is already booked
without a name of its own: Error: seat 12A is already booked

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node custom-class.mjs

super(message, options) runs the Error constructor, which stores the message, records the stack and, when the options hold one, the cause. The constructor then sets name, because a subclass otherwise inherits the name 'Error' from Error.prototype: the last line of the output shows what NoName looks like in a log, where nothing distinguishes it from any other error. seat is the data the caller needs. The catch block checks the class, and rethrows anything else, so a bug in book would not pass for a booked seat.

Setting the name so that it survives the build

There are three common ways to set the name, and each one works:

  • this.name = 'SeatTakenError' in the constructor, as above;
  • a class field, name = 'SeatTakenError';, which does the same for every instance;
  • an assignment on the prototype, SeatTakenError.prototype.name = 'SeatTakenError';, which is how the built-in classes hold their names.

The first two give every error an own, enumerable name property, which JSON.stringify and Object.keys then see; the third keeps one shared name on the prototype. What does not work reliably is copying the name from the class with this.name = new.target.name or this.constructor.name, because minifiers rename classes to save bytes:

Names copied from classes, before and after minification

JavaScript · minified-names.mjs

// The same two error classes, as written and as a minifier ships them (dist/name-from-class.min.mjs was made with
// esbuild 0.28.2: --bundle --minify --format=esm).
import * as written from './name-from-class.mjs';
import * as shipped from './dist/name-from-class.min.mjs';

for (const [label, errors] of [['as written', written], ['as shipped', shipped]]) {
  const seat = new errors.SeatTakenError('seat 12A is already booked');
  const card = new errors.PaymentDeclinedError('the bank declined the card');
  console.log(`${label}: ${seat.name}, ${card.name}`);
}

Output

as written: SeatTakenError, PaymentDeclinedError
as shipped: t, x

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node minified-names.mjs

As written · name-from-class.mjs

// Error classes that copy their name from the class. Fine in this file; the bundler's minified copy is another story.
class AppError extends Error {
  constructor(message, options) {
    super(message, options);
    this.name = new.target.name; // the name of the class that was called with new
  }
}

export class SeatTakenError extends AppError {}
export class PaymentDeclinedError extends AppError {}

As shipped · dist/name-from-class.min.mjs

var s=class extends Error{constructor(c,n){super(c,n),this.name=new.target.name}},t=class extends s{},x=class extends s{};export{x as PaymentDeclinedError,t as SeatTakenError};

In the minified copy the classes are called t and x, so the errors are named after them, and an error dashboard would group production failures under those letters. Bundlers can keep class names, through esbuild’s keep-names setting or terser’s keep_classnames, but a name written out as a string is correct whatever the build does.

Wrapping a failure with cause

When code catches a low-level error and throws one of its own, the new error should say what the program was trying to do, and keep the old one, which says what actually broke. The cause option links them:

When an error gets a cause property JavaScript · cause-rules.mjs
// When an error gets a cause property, and what it looks like.
const original = new SyntaxError('bad JSON');
const wrapped = new Error('config.json is not valid JSON', { cause: original });
console.log('wrapped.cause is the original:', wrapped.cause === original);
console.log('own property:', Object.hasOwn(wrapped, 'cause'), '| enumerable:', Object.getOwnPropertyDescriptor(wrapped, 'cause').enumerable);
console.log('no options, a cause property?', Object.hasOwn(new Error('x'), 'cause'));
console.log('{ cause: undefined }, a cause property?', Object.hasOwn(new Error('x', { cause: undefined }), 'cause'));
console.log('a cause can be any value:', JSON.stringify(new Error('retry later', { cause: { status: 503, after: 30 } }).cause));

Output

wrapped.cause is the original: true
own property: true | enumerable: false
no options, a cause property? false
{ cause: undefined }, a cause property? true
a cause can be any value: {"status":503,"after":30}

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node cause-rules.mjs

The rules come from the Error constructor in ECMA-262. An error gets a cause property only when the options object has one, even if its value is undefined. The property is the error’s own, and it is not enumerable, so loops and JSON.stringify skip it. Its value can be anything: usually the caught error, sometimes a small object of facts such as a status code.

A chain of three errors linked by cause: an HttpError, the TypeError that caused it, and the connection error that came first.The caller checks the outererror's class and statusHttpErrorHTTP 503 for /api/seatsstatus: 503, url: /api/seatsTypeErrorfetch failedErrorconnect ECONNREFUSED 10.0.0.7:443code: ECONNREFUSED(the first failure)catchescausecause

A chain of causes

Text description of the diagram

The chart shows one failure as three linked errors, from top to bottom.

- The caller catches the outermost error and checks its class and its `status`. - That error is an `HttpError` with the message "HTTP 503 for /api/seats" and the fields `status: 503` and `url: /api/seats`. Its `cause` is the error it was created to report. - The cause is a `TypeError` with the message "fetch failed", and its own `cause` is the first failure. - The first failure is an `Error` with the message "connect ECONNREFUSED 10.0.0.7:443" and the code `ECONNREFUSED`: the connection was refused.

Code decides what to do from the outermost error; a log follows `cause` from there down to the first failure.

A config loader shows the pattern at work. It turns two different low-level failures into one class of its own, with a message about the file, and keeps each original failure as the cause:

A config loader that wraps what went wrong JavaScript · error-chain.mjs
// A config loader that wraps a low-level failure in an error of its own and keeps the original as its cause.
class ConfigError extends Error {
  constructor(message, options) {
    super(message, options);
    this.name = 'ConfigError';
  }
}

const files = new Map([['config.json', '{ "port": 8080, }']]); // stands in for the file system

function readText(path) {
  if (!files.has(path)) throw new Error(`no such file: ${path}`);
  return files.get(path);
}

function loadConfig(path) {
  let text;
  try {
    text = readText(path);
  } catch (error) {
    throw new ConfigError(`could not read ${path}`, { cause: error });
  }
  try {
    return JSON.parse(text);
  } catch (error) {
    throw new ConfigError(`${path} is not valid JSON`, { cause: error });
  }
}

/** One line per error of the chain, following cause from the outermost error inwards. */
function chain(error) {
  const lines = [];
  for (let e = error, depth = 0; e !== undefined && depth < 10; e = e.cause, depth++) {
    lines.push(`${depth === 0 ? '' : 'caused by '}${e.name}: ${e.message}`);
  }
  return lines.join('\n');
}

for (const path of ['config.json', 'settings.json']) {
  try {
    loadConfig(path);
  } catch (error) {
    console.log(chain(error));
  }
}

Output

ConfigError: config.json is not valid JSON
caused by SyntaxError: Expected double-quoted property name in JSON at position 16 (line 1 column 17)
ConfigError: could not read settings.json
caused by Error: no such file: settings.json

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node error-chain.mjs

Version note

In the Run button’s QuickJS engine the SyntaxError from JSON.parse says expecting property name; the two ConfigError lines and the missing-file error are the same, because this program wrote those messages itself.

In your browser, QuickJS (quickjs-emscripten 0.32.0) prints:

ConfigError: config.json is not valid JSON
caused by SyntaxError: expecting property name
ConfigError: could not read settings.json
caused by Error: no such file: settings.json

The caller needs to know one thing, that loading the configuration failed, and gets it from the outer class. Whoever reads the log needs to know why, and chain follows cause down to the first failure. Node.js does the same when it prints an error: console.log shows every cause after the label [cause].

How Node.js prints a cause JavaScript · node-inspect-cause.mjs
// How Node.js prints an error with a cause. Stack traces are switched off here (Error.stackTraceLimit = 0, a V8
// setting) so that only the structure shows.
Error.stackTraceLimit = 0;
class ConfigError extends Error {
  constructor(message, options) {
    super(message, options);
    this.name = 'ConfigError';
  }
}
const error = new ConfigError('config.json is not valid JSON', { cause: new SyntaxError('Unexpected end of JSON input') });
console.log(error);

Output

[ConfigError: config.json is not valid JSON] {
  [cause]: [SyntaxError: Unexpected end of JSON input]
}

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node node-inspect-cause.mjs

Three habits make wrapping worth it. Write the new message about the operation (could not read settings.json), not about the symptom. Choose the class for the caller who has to react to it. And always pass the caught error as cause: ESLint’s preserve-caught-error rule, part of its recommended set, reports a catch block that throws a new error without it.

Recognising error objects: instanceof and Error.isError

error instanceof Error asks whether Error.prototype is in the value’s prototype chain. That is usually the same as asking whether the value is an error, but not always:

Real, fake and disguised errors JavaScript · is-error.mjs
// instanceof asks about the prototype chain; Error.isError asks whether the value really is an error object.
const real = new TypeError('a real error');
const fake = Object.create(Error.prototype, { message: { value: 'not an error at all' } });
const disguised = { [Symbol.toStringTag]: 'Error', message: 'pretends to be one' };

for (const [label, value] of [['real', real], ['fake', fake], ['disguised', disguised], ['a string', 'boom']]) {
  console.log(
    `${label.padEnd(10)} instanceof Error: ${String(value instanceof Error).padEnd(5)} Error.isError: ${String(Error.isError(value)).padEnd(5)} toString tag: ${Object.prototype.toString.call(value)}`,
  );
}

/** What a catch block can do with anything thrown: keep real errors, wrap everything else. */
function asError(value) {
  return Error.isError(value) ? value : new Error(`a non-error value was thrown: ${String(value)}`, { cause: value });
}
console.log(asError('boom').message, '| cause:', asError('boom').cause);

Output

real       instanceof Error: true  Error.isError: true  toString tag: [object Error]
fake       instanceof Error: true  Error.isError: false toString tag: [object Object]
disguised  instanceof Error: false Error.isError: false toString tag: [object Error]
a string   instanceof Error: false Error.isError: false toString tag: [object String]
a non-error value was thrown: boom | cause: boom

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node is-error.mjs

The fake object has Error.prototype in its chain, so instanceof accepts it, although no Error constructor ever made it. The disguised object borrows the tag Object.prototype.toString reports, the old way of recognising errors. Error.isError is fooled by neither: ECMA-262 defines it as a check of an internal slot that only the error constructors set, so it returns true exactly for objects that an error constructor created. The asError helper at the end shows a common use, in a catch block that must cope with anything thrown: keep real errors, and wrap any other value in an Error with the value as its cause.

The opposite mistake happens across realms. Each realm, such as an iframe in a browser or a node:vm context, has its own set of built-in constructors, so an error made in one is not an instance of another realm’s Error:

An error from another realm JavaScript · cross-realm.mjs
// An error made in another realm (here a node:vm context; in a browser, an iframe) fails instanceof but not Error.isError.
import { runInNewContext } from 'node:vm';

const foreign = runInNewContext('new TypeError("made in another realm")');
console.log('instanceof TypeError:', foreign instanceof TypeError);
console.log('instanceof Error:', foreign instanceof Error);
console.log('Error.isError:', Error.isError(foreign));
console.log('name:', foreign.name);

Output

instanceof TypeError: false
instanceof Error: false
Error.isError: true
name: TypeError

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node cross-realm.mjs

instanceof says no twice, and Error.isError gives the right answer. Node.js 24 documents util.types.isNativeError as deprecated in favour of Error.isError. Node.js 24 and the Run button’s QuickJS engine have it, while MDN still lists it as not available in every widely used browser, so check your targets, or use one of the polyfills it names. Some code also calls Error.captureStackTrace(this, SomeError), an API that V8 introduced to leave helper functions out of a stack; it is a Stage 2 proposal at TC39, not part of the standard, and a class that extends Error does not need it.

Errors in logs and API responses

JSON.stringify writes an object’s own enumerable properties, and an error’s message, stack and cause are none of those. Writing errors into a log as JSON therefore needs a function of its own:

An error, its fields and its cause as JSON JavaScript · serialize-error.mjs
// JSON.stringify sees only own enumerable properties, so errors need a serializer of their own for logs.
class PaymentError extends Error {
  constructor(message, { code, ...options } = {}) {
    super(message, options);
    this.name = 'PaymentError';
    this.code = code;
  }
}

const error = new PaymentError('card declined', { code: 'CARD_DECLINED', cause: new Error('issuer said no') });
console.log('JSON.stringify(new Error):', JSON.stringify(new Error('lost')));
console.log('JSON.stringify(PaymentError):', JSON.stringify(error));

/** name, message, every own field and the cause chain, at most `depth` levels deep. */
function serializeError(value, depth = 5) {
  if (!Error.isError(value)) return value;
  const out = { name: value.name, message: value.message };
  for (const key of Object.keys(value)) if (key !== 'name') out[key] = value[key];
  if ('cause' in value) out.cause = depth > 0 ? serializeError(value.cause, depth - 1) : '[cause chain too deep]';
  return out;
}
console.log(JSON.stringify(serializeError(error), null, 2));

Output

JSON.stringify(new Error): {}
JSON.stringify(PaymentError): {"name":"PaymentError","code":"CARD_DECLINED"}
{
  "name": "PaymentError",
  "message": "card declined",
  "code": "CARD_DECLINED",
  "cause": {
    "name": "Error",
    "message": "issuer said no"
  }
}

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node serialize-error.mjs

A plain Error comes out as {}, and the custom error keeps only the fields its constructor assigned, without its message. serializeError writes the name and the message, every own field such as code, and the cause chain, with a depth limit so that a chain which loops back on itself cannot run for ever. Two cautions apply before such an object leaves the program: a stack trace reveals file paths and code structure, so it belongs in internal logs rather than in a response to a browser, and fields copied from a request can hold personal data or tokens, which the next lesson keeps out of logs.

JSON Formatter Paste a serialized error from a log to read its nested causes as an indented tree.

Exercise: an HttpError and the chain of its causes

Exercise · Medium · JavaScript

An HttpError and the chain of its causes

A booking page calls an API, and when a request fails it throws an HttpError. Finish the class and write describeError, which turns an error and its causes into text for a log.

HttpError:

  • new HttpError(status, url, options) is an Error whose name is 'HttpError';
  • it has status and url properties, and its message is HTTP <status> for <url>, such as HTTP 503 for /api/seats;
  • it keeps a cause exactly as Error does: new HttpError(503, url, { cause: error }) has that cause, and an HttpError made without the option has no cause property at all.

describeError(error) returns one line per error, outermost first, joined with \n:

  • the first line is <name>: <message>, and every cause after it is caused by <name>: <message>;
  • a cause that is not an error object (check it with Error.isError) is printed as JSON, such as caused by {"retryAfter":30};
  • stop when there is no cause property; a chain that comes back to an error already printed, or that is more than 10 causes long, must not loop for ever.

Starter code · http-error.mjs

/**
 * A failed HTTP request: the status code, the URL and, when there was one, the error that caused it.
 * Fix this class and write describeError below it.
 */
export class HttpError extends Error {
  constructor(status, url) {
    super('request failed');
  }
}

/**
 * The error and every cause behind it, one line each, outermost first:
 *   HttpError: HTTP 503 for /api/seats
 *   caused by TypeError: fetch failed
 * @param {unknown} error
 * @returns {string}
 */
export function describeError(error) {
  return String(error);
}
The sample tests · http-error.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { HttpError, describeError } from './http-error.mjs';

test('an HttpError is an Error with its own name, status and url', () => {
  const error = new HttpError(404, '/api/seats/12A');
  assert.ok(error instanceof HttpError && error instanceof Error, 'expected an instance of HttpError and Error');
  assert.equal(error.name, 'HttpError');
  assert.equal(error.status, 404);
  assert.equal(error.url, '/api/seats/12A');
});

test('the message names the status and the url', () => {
  assert.equal(new HttpError(503, '/api/seats').message, 'HTTP 503 for /api/seats');
});

test('a cause is kept when one is given, and only then', () => {
  const low = new TypeError('fetch failed');
  assert.equal(new HttpError(503, '/api/seats', { cause: low }).cause, low);
  assert.equal(Object.hasOwn(new HttpError(503, '/api/seats'), 'cause'), false, 'no cause option, no cause property');
});

test('describeError prints one line per error, outermost first', () => {
  const root = new Error('connect ECONNREFUSED 10.0.0.7:443');
  const middle = new TypeError('fetch failed', { cause: root });
  const top = new HttpError(503, '/api/seats', { cause: middle });
  assert.equal(describeError(top), 'HttpError: HTTP 503 for /api/seats\ncaused by TypeError: fetch failed\ncaused by Error: connect ECONNREFUSED 10.0.0.7:443');
});

test('describeError of an error without a cause is a single line', () => {
  assert.equal(describeError(new RangeError('page 99 does not exist')), 'RangeError: page 99 does not exist');
});

test('a cause that is not an error is printed as JSON', () => {
  const error = new HttpError(429, '/api/seats', { cause: { retryAfter: 30 } });
  assert.equal(describeError(error), 'HttpError: HTTP 429 for /api/seats\ncaused by {"retryAfter":30}');
});
A hint

Pass options straight on to super(message, options): the Error constructor creates cause only when the options have one, which is exactly the rule the tests check. For describeError, walk the chain with a loop, current = current.cause, and keep the errors already printed in a Set so that a chain that loops can stop.

The sample tests run on this device, in your browser (QuickJS): nothing is sent to mysmartcopilot.com. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Self-check

Check yourself

7 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 7 What does minified-names.mjs print? Decide before you look at its recorded output.

    What does this program print? Choose one answer.

    // The same two error classes, as written and as a minifier ships them (dist/name-from-class.min.mjs was made with
    // esbuild 0.28.2: --bundle --minify --format=esm).
    import * as written from './name-from-class.mjs';
    import * as shipped from './dist/name-from-class.min.mjs';
    
    for (const [label, errors] of [['as written', written], ['as shipped', shipped]]) {
      const seat = new errors.SeatTakenError('seat 12A is already booked');
      const card = new errors.PaymentDeclinedError('the bank declined the card');
      console.log(`${label}: ${seat.name}, ${card.name}`);
    }
    Show the answer to question 1

    Answer: it prints

    as written: SeatTakenError, PaymentDeclinedError
    as shipped: t, x

    new.target is the class that was called with new, so the source names come out right. The minifier renamed the classes to t and x and exported them under their old names, so new.target.name reports the short names. A name written out as a string would survive.

  2. Question 2 of 7 Which of these errors has no cause property at all?

    Choose one answer.

    Show the answer to question 2

    Answer: new Error('x', {})

    The Error constructor creates cause only when the options object has a cause property, whatever its value, undefined included. An empty options object has none, so the error gets none, as cause-rules.mjs shows.

  3. Question 3 of 7 class QuotaError extends Error {} sets no name. What is String(new QuotaError('storage is full'))?

    Choose one answer.

    Show the answer to question 3

    Answer: 'Error: storage is full'

    String() uses Error.prototype.toString, which joins name and message. The subclass inherits name from Error.prototype, which is 'Error', so nothing in the text says which class it was. Set the name in the constructor, in a class field or on the prototype.

  4. Question 4 of 7 What do obj instanceof Error and Error.isError(obj) return for obj = Object.create(Error.prototype)?

    Choose one answer.

    Show the answer to question 4

    Answer: true and false

    instanceof only looks for Error.prototype in the prototype chain, which Object.create put there. Error.isError checks the internal slot that only the error constructors set, and Object.create never ran one, so the object is not an error.

  5. Question 5 of 7 A TypeError created in an iframe, or in a node:vm context, reaches code in the main page or program. Which check recognises it as an error?

    Choose one answer.

    Show the answer to question 5

    Answer: Error.isError(error)

    Each realm has its own Error and TypeError constructors, so an error made in another realm is not an instance of this realm's classes, as cross-realm.mjs shows. Error.isError checks the object itself and answers true, and error.name still says 'TypeError'.

  6. Question 6 of 7 What does JSON.stringify(new Error('lost')) return?

    Choose one answer.

    Show the answer to question 6

    Answer: '{}'

    JSON.stringify writes own enumerable properties, and message is a non-enumerable own property, name lives on the prototype, and stack is not enumerable either. An error needs a serializer of its own before it goes into a JSON log.

  7. Question 7 of 7 What does this catch block lose?

    Read the code, then choose one answer.

    try {
      saveDraft(text);
    } catch (error) {
      throw new Error('could not save the draft');
    }
    Show the answer to question 7

    Answer: The original error, with its message and its stack trace

    The new error records a stack that starts in the catch block, and nothing links it to the error that was caught, so the real reason the save failed is gone. Passing { cause: error } keeps it, and ESLint's preserve-caught-error rule reports the version without it.

Interview questions

Fresher · What does the cause option of the Error constructor do, and why pass it when you rethrow?

new Error(message, { cause }), standard since ECMAScript 2022, stores the value of cause on the new error as a cause property, which is the error’s own and not enumerable; the property exists only when the options object has one. Its purpose is to link a high-level error to the failure that produced it. When a function catches, say, a SyntaxError from JSON.parse and throws a ConfigError that names the file, the caller gets a clear class and message, and the original error, with its own message and stack, stays reachable as error.cause. Without it, the rethrow discards the evidence of what actually broke. Node.js prints causes when it logs an error, and ESLint’s preserve-caught-error rule reports a catch block that throws a new error without one.

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Mid to senior

    After the front end started shipping minified bundles, the error dashboard groups production failures under names such as "t" and "x" instead of SeatTakenError and PaymentDeclinedError. What happened, and how would you fix the error classes and keep it from coming back?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    An API client library exports HttpError. In one service, error instanceof HttpError is false for errors the client clearly threw, while the same check works in other services. Give two likely causes, and make the check reliable.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • A custom error class extends Error, passes the message and options to super, sets name and adds the fields callers need; callers check it with instanceof and rethrow anything else.
  • Write the name out as a string: minifiers rename classes, so new.target.name and constructor.name change in production builds.
  • new Error(message, { cause }) keeps the original failure as an own, non-enumerable cause; wrap errors to add what the program was doing, and always pass the caught error along.
  • Error.isError checks whether a value really is an error object: it rejects fakes that instanceof accepts and accepts errors from other realms that instanceof rejects.
  • JSON.stringify drops an error’s message, stack and cause; serialize errors with a function that writes them, and keep stack traces and personal data out of anything a browser receives.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.