Debugging errors is the hardest part of programming. Errors can appear in your code in a variety of ways, whether as syntax errors, errors in logic, or the most dreaded of all, runtime errors. Runtime errors occur whenever something unexpected occurs in your application, and they often lead to catastrophic issues that can crash your program.
Like many languages, Node.js provides a mechanism to anticipate errors before they occur. When an error occurs in your code, it turns into an object called an exception. Properly handling these exceptions allows you to recover gracefully from unforeseen issues, resulting in a much better user experience.
Table of contents
- Creating exceptions
- Error objects
- Handling exceptions
- Catching uncaught exceptions
- Exceptions with promises
- error handling in synchronous
- error handling in express
Creating exceptions
An exception is created using the throw keyword:
As soon as JavaScript executes this line, the normal program flow is halted and the control is held back to the nearest exception handler.
Usually in client-side code value can be any JavaScript value including a string, a number or an object.
In Node.js, we don't throw strings, we just throw Error objects.
Error objects
An error object is an object that is either an instance of the Error object, or extends the Error class, provided in the Error core module:
Handling exceptions
An exception handler is a try/catch statement.
Any exception raised in the lines of code included in the try block is handled in the corresponding catch block:
try {
// lines of code
} catch (e) {}
e in this example is the exception value.
You can add multiple handlers, that can catch different kinds of errors.
Exceptions with promises
Using promises you can chain different operations, and handle errors at the end:
performFunction1()
.then(performFunction2)
.then(performFunction3)
.catch(err => console.error(err));
How do you know where the error occurred? You don't really know, but you can handle errors in each of the functions you call (performfunction(x)), and inside the error handler throw a new error, that's going to call the outside catch handler:
const performFunction1 = () => {
// ...
try {
// ...
} catch (err) {
// ... handle it locally
throw new Error(err.message);
}
// ...
};
To be able to handle errors locally without handling them in the function we call, we can break the chain. You can create a function in each then() and process the exception:
performFunction1()
.then(() => {
return performFunction2().catch(err => {
// handle error
throw err; // break the chain!
});
})
.then(() => {
return performFunction3().catch(err => {
// handle error
throw err; // break the chain!
});
})
.catch(err => console.error(err));
Error Handling in express
Error Handling refers to how Express catches and processes errors that occur both synchronously and asynchronously. Express comes with a default error handler so you don’t need to write your own to get started.
It’s important to ensure that Express catches all errors that occur while running route handlers and middleware.
Errors that occur in synchronous code inside route handlers and middleware require no extra work. If synchronous code throws an error, then Express will catch and process it. For example:
app.get('/', (req, res) => {
throw new Error('BROKEN') // Express will catch this on its own.
})
For errors returned from asynchronous functions invoked by route handlers and middleware, you must pass them to the next() function, where Express will catch and process them. For example:
app.get('/', (req, res, next) => {
fs.readFile('/file-does-not-exist', (err, data) => {
if (err) {
next(err) // Pass errors to Express.
} else {
res.send(data)
}
})
})
error in asynchronous code, route handlers and middleware that return a Promise will call next(value) automatically when they reject or throw an error. For example:
app.get('/user/:id', async (req, res, next) => {
try{
const user = await getUserById(req.params.id)
res.send(user)}
catch(err){next(err)}
})
Top comments (0)