I have to admit that this is something that I often forget to do apply it to my normal coding, and I’m not here to say that you should create a ton of custom errors instances in your code, but as everything, when use it for the right reason, it can make your code more readable in my opinion.
But let’s give a quick introduction to custom errors.
Error handling with try..catch
statements
A good practice when writing code, and this is just not related to JavaScript, but to any language, is to try to catch errors in your code, that could be guarding variables, or checking if an API request failed for some reasons.
Now let’s imagine we have a simple function that check a name against a valid list of names, and it logs if is valid or not, and what our function should do is to check a few things:
- The name is entered and logs a message if not
- The name is in the list
- It handles the case when the name is not in the list
And if you use a try..catch
statement, your function will probably look something like this:
const isNameRight = name => {
const validNames = ['joe', 'joan', 'jim'];
try {
if (!name) throw new Error('You didn\'t pass a name!');
if (!validNames.includes(name)) throw new Error('This name is not valid!');
console.log('Yes, it is right');
} catch (error) {
console.error(error.message);
}
}
isNameRight(); // You didn't pass a name!
isNameRight('joe'); // Yes, it is right
isNameRight('alex'); // This name is not valid!
Now, if you just want to show a message, the code is fine as it is, but in some cases you want to do something more in base of the type of error.
For example the first error, is more a guard error type, that just checks if you are passing the argument needed by the function, while the second error is just a response type according to the logic of our function.
How to create a custom Error
But before we dwell in how to change our code above, we should explain first how to create a custom Error
.
There are many ways to do it, one way to do it is just to create an instance of the Error class and change the name, for example:
// create custom error
const myCustomError = new Error('Some error message');
myCustomError.name = 'myCustomError';
// then in your code
try {
if (condition) {
throw myCustomError;
}
// rest of the code
} catch (error) {
if (error.name === 'myCustomError') {
console.error('Do something custom');
} else {
console.error(error.message);
}
}
The code above is fine, but personally I prefer to just create a new instance, as it feels a much cleaner way to handle it for me, here how you should do it:
// With a message and a name
class MyCustomError extends Error {
constructor(message) {
super(message);
this.name = 'MyCustomError';
}
};
// then you use it like this
throw new MyCustomError('Some message');
But if you want you can shrink it down to something more concise:
// With no message and shorter
class MyCustomError extends Error {};
// then you use it like this
throw new MyCustomError('Some message');
Let’s refactor!
So now let’s try to refactor our initial function using custom errors.
First thing we need the two custom Error classes:
// Guard
class InputError extends Error {};
// Error for invalid name
class InvalidNameError extends Error {};
Now we can refactor our original function to this:
const isNameRight = name => {
const validNames = ['joe', 'joan', 'jim'];
try {
if (!name) throw new InputError;
if (!validNames.includes(name)) throw new InvalidNameError;
console.log('Yes, it is right');
} catch (error) {
if (error instanceof InputError) {
console.error('You didn\'t pass a name!');
// some extra logic if needed
} else if (error instanceof InvalidNameError) {
console.error('This name is not valid!');
// some different extra logic
} else {
// catch any other cases
console.error(error.message);
}
}
}
The code above has a few pros:
- You can reuse the Error classes for other functions
- You can handle the different errors executing different actions for each of the errors
- It’s easier to test and debug as you know exactly that it is failing
But also a few negatives:
- Code is more verbose
- You probably don’t need it if the logic is very simple and you just need to print an error.
A real world example
As the code above seems more theoretical, I wanted to put a more real world scenario, where we have a function that takes some arguments and calls some API.
// errors.js
export class APIRequestError extends Error {}
export class InputError extends Error {}
// request.js
import { APIRequestError, InputError } from './errors.js';
const addToAPI = async (name, age) => {
try {
if (!name || !age) throw new InputError;
const { data } = await axios.post('some/api', {
name,
age,
}).catch(error => {
throw new APIRequestError(error.message);
});
} catch (error) {
if(error instanceof InputError) {
// do something to handle missing inputs
} else if (error instanceof APIRequestError) {
// do something else to handle API failure
}
}
}
Conclusions
So this is a simple example, and it’s not the only way to implement this, instead of using instanceof
you could achieve the same using the instance name, for example:
} catch (error) {
if (error.name === 'InputError') {
console.error('You didn\'t pass a name!');
// some extra logic if needed
} else if (error.name === 'InvalidNameError') {
console.error('This name is not valid!');
// some different extra logic
} else {
// catch any other cases
console.error(error.message);
}
}
It’s exactly the same, just a different way.
And again, there are always different ways to handle this problem, and this is not just the absolute best way to do it, it’s just one way to do it.
Should I use this technique all the times?
No. You should use it only when it makes sense for your application.
As everything, don’t learn one technique and copy/paste it all the time, just learn when is more appropriate to use it.
I hope this article helped you to better understand how to use custom Errors in JavaScript. And as always any comments are welcome.
Top comments (1)
Yeah.. This was helpful.. I usually pass a single error message regardless of what kind of error it is. This was more defined where the Error class was extended and different child classes were created.