Custom error classes
The built-in Error is enough to report a problem, but it does not always say which problem it is. An invalid email, a missing product and a network failure call for different reactions. Custom error classes let you tell them apart.
An error class
An error is an object of the Error class, so your own error is created by inheritance. Set the name property to the class name — then it shows up in messages:
class ValidationError extends Error {
name = 'ValidationError';
}
const error = new ValidationError('Invalid email');
console.log(error.name); // ValidationError
console.log(error.message); // Invalid emailYou do not need a constructor: the message is accepted and stored by the parent Error constructor.
Telling errors apart
The instanceof operator checks which class an error belongs to. This way one catch can handle different problems differently:
class ValidationError extends Error {
name = 'ValidationError';
}
function checkAge(age) {
if (age < 0) {
throw new ValidationError('Age cannot be negative');
}
return age;
}
try {
checkAge(-5);
} catch (error) {
if (error instanceof ValidationError) {
console.log(`Please check the data: ${error.message}`); // Please check the data: Age cannot be negative
}
}Extra data
You can add properties to your error — for example, the name of the form field that is wrong. For this, write a constructor and call super(message) first:
class ValidationError extends Error {
name = 'ValidationError';
constructor(field, message) {
super(message);
this.field = field;
}
}
const error = new ValidationError('email', 'Wrong format');
console.log(`${error.field}: ${error.message}`); // email: Wrong formatRethrowing
If catch gets an error it cannot handle, it throws it again with throw. Then the error reaches code that knows what to do with it:
class ValidationError extends Error {
name = 'ValidationError';
}
function handle(action) {
try {
action();
} catch (error) {
if (error instanceof ValidationError) {
return 'Bad data';
}
throw error;
}
}
try {
handle(() => {
throw new TypeError('Unexpected failure');
});
} catch (error) {
console.log(error.name); // TypeError
}The cause of an error
Sometimes one error is caused by another. As the second argument of Error you can pass an object with a cause field — the original error. Then the whole chain is visible when you investigate the problem:
try {
try {
JSON.parse('{ broken }');
} catch (error) {
throw new Error('Could not load the settings', { cause: error });
}
} catch (error) {
console.log(error.message); // Could not load the settings
console.log(error.cause.name); // SyntaxError
}Common mistake
You can throw any value, even a string, but you should not. A string has no name, message or call stack — it does not tell you where the problem happened:
try {
throw 'Something went wrong';
} catch (error) {
console.log(error.message); // undefined
}Always throw Error objects or objects of your own subclasses.