Skip to content
Theory · Errors · lesson 7 of 10

Custom error classes

A chain of tasks carries notes of two sorts. Some report what the assistant can handle himself: "the client gave no name — put in 'guest'". Others report a real breakdown, and the manager must see it. To keep them apart, the assistant writes his own notes on his own letterhead and recognizes them at first sight.

Without such a letterhead, errors in a program look alike. The function checkName throws an Error when the name is empty. But a mistake in the program itself can happen in the same try, and catch gets both the same way:

JavaScript
function checkName(name) {
    if (name.length === 0) {
        throw new Error("name is empty");
    }
    return name;
}

try {
    checkName(null);
} catch (e) {
    console.log(e.message); // Cannot read properties of null (reading 'length')
}

Let's see what happened:

  1. checkName(null) tries to read name.length, but null has no properties.
  2. JavaScript itself throws a TypeError, and catch catches it.
  3. catch prints the built-in error's text — this is the Node and Chrome text.

For "" the catch block would get the text name is empty, and for null a TypeError from reading name.length. The first error means "the user entered no name", the second "there is a bug in the program". The check instanceof Error is true for both, and the first one's e.name is "Error", as for any other error made with new Error. Comparing message texts is unreliable: they change with program edits, and other browsers word the built-in message above differently from Node and Chrome.

An error class

Your own letterhead is your own kind of error: a class that inherits from Error.

JavaScript
class ValidationError extends Error {
    constructor(message) {
        super(message);
        this.name = "ValidationError";
    }
}

const e = new ValidationError("name is empty");
console.log(e.name); // ValidationError
console.log(e.message); // name is empty
console.log(e instanceof ValidationError, e instanceof Error); // true true

Let's take the class apart:

  1. extends Error makes ValidationError a kind of error.
  2. The call super(message) passes the text to the Error constructor, which stores it in message.
  3. The line this.name = "ValidationError" sets the kind of error that e.name returns.

An instance of such a class is still an Error, so the check instanceof Error is true for it as well. The constructor can store its own properties as well, such as this.field = "name", to remember which form field was filled in wrong.

Catching your own error

Now the check for an empty name throws new ValidationError("name is empty") instead of new Error. The catch block still receives every error, but handles only this kind and rethrows any other:

JavaScript
function nameOrGuest(name) {
    try {
        if (name.length === 0) {
            throw new ValidationError("name is empty");
        }
        return name;
    } catch (e) {
        if (e instanceof ValidationError) {
            return "guest";
        }
        throw e;
    }
}

console.log(nameOrGuest("Ann"), nameOrGuest("")); // Ann guest

Let's go through both calls:

  1. nameOrGuest("Ann") passes the check and returns "Ann", and catch does not run.
  2. nameOrGuest("") throws a ValidationError.
  3. catch receives it, the check instanceof ValidationError is true, and the function returns "guest".

The choice is made by the if inside catch, not by catch itself: it catches everything thrown in try.

Here the letterhead picture stops working. The assistant recognizes his letterhead at first sight, while catch recognizes your own error only through the instanceof check you write yourself.

Rethrowing

The line throw e throws the caught object once more, unchanged. The call nameOrGuest(null) does not turn a bug into a guest: reading name.length throws a TypeError, and it goes out. If nobody above catches it, the program stops.

Without the line throw e the bug would vanish quietly: the function would return undefined, and nobody would learn about it. Code catches only the errors it can handle and lets the rest pass on.

Interviewers ask why you would create your own error classes. The answer: so that catch tells an expected error from a bug with instanceof and does not swallow other errors.

Common mistake

The constructor does not set name:

JavaScript
class ValidationError extends Error {
    constructor(message) {
        super(message); // ✗
    }
}

There is no error message, and the check instanceof ValidationError still works. But e.name does not name the kind of error: the instance's name is "Error", inherited from the parent. Code that tells errors apart by e.name takes such an error for an ordinary Error. After super(message) the line this.name = "ValidationError"; is needed.

Key points

  • Your own kind of error is a class that inherits from Error and sets this.name after super(message).
  • catch receives every error, and it recognizes your own with instanceof.
  • An error the code cannot handle is passed on with throw e.

Subscribe to take the course

The course is free. Sign in with Telegram and subscribe to the channel to open every lesson.

Sign in another way