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:
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:
checkName(null)tries to readname.length, butnullhas no properties.- JavaScript itself throws a
TypeError, andcatchcatches it. catchprints 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.
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 trueLet's take the class apart:
extends ErrormakesValidationErrora kind of error.- The call
super(message)passes the text to theErrorconstructor, which stores it inmessage. - The line
this.name = "ValidationError"sets the kind of error thate.namereturns.
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:
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 guestLet's go through both calls:
nameOrGuest("Ann")passes the check and returns"Ann", andcatchdoes not run.nameOrGuest("")throws aValidationError.catchreceives it, the checkinstanceof ValidationErroris 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:
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
Errorand setsthis.nameaftersuper(message). catchreceives every error, and it recognizes your own withinstanceof.- 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.