Page Content

Tutorials

What is Error Handling in Node.js? With Code Example

Error Handling in Node.js

Using try...catch blocks, sending errors as the first input to callbacks, and emitting error events via EventEmitter are the three main methods that Node.js provides for handling exceptions and errors. Building reliable applications requires an understanding of these mechanisms. In Node.js, an exception is a critical system issue that signals an unstable state and typically calls for a graceful shutdown and restart, while an error condition is a non-fatal scenario that should be detected and handled.

Blocks for Synchronous Error Handling

Specifically, the try...catch block is made to catch exceptions that are thrown during synchronous code execution. A caller (or its caller) can intercept the error if they employ try...catch. The application will crash if there isn’t a try...catch block.

Here’s a basic example of a try...catch block handling a synchronous error:

Code Example:

try {
    var a = 1;
    b++; // This will cause an error because 'b' is undefined
    console.log(b); // This line will not be executed
} catch (error) {
    console.log(error); // Here we handle the error caused in the try block
}

Code Output:

[ReferenceError: b is not defined]

You can also modify the error object’s properties, such as its message, before re-throwing it:

Code Example:

try {
    var a = 1;
    b++;
    console.log(b);
} catch (error) {
    error.message = "b variable is undefined, so the undefined can't be incremented";
    throw error; // Re-throws the modified error
}

Code Output:

[Error: b variable is undefined, so the undefined can't be incremented

Unhandled Exceptions and Preventing Application Crashes

During application development, uncaught exceptions are a major concern because Node.js operates on a single process. Node.js crashes if exceptions are ignored.

Use process.on('uncaughtException') to connect a handler to catch uncaught exceptions.

Code Example:

process.on('uncaughtException', function (err) {
  console.log('Caught exception: ' + err);
});
setTimeout(function() {
  console.log("The exception was caught and this can run.");
}, 1000);
throwAnUncaughtException(); // This function is not defined and will cause an uncaught exception

Code Output:

Caught exception: ReferenceError: throwAnUncaughtException is not defined
The exception was caught and this can run.

Although it seems to be an anomaly, this is typically regarded as a bad practice for production systems.

  • Correct resolution may be impeded if the error’s underlying cause is not identified.
  • If a database connection pool shuts, for instance, the server may continue but not have a working connection, which can result in a continuous propagation of errors.
  • Other asynchronous operations (such as the setTimeout in the example) may still fire even though code after the toss statement won’t run, resulting in an inconsistent application state.

A better strategy when an uncaughtException occurs is to terminate the process gracefully, allowing a process manager to restart the server in its initial working state. Tools like forever or PM2 (Process Manager 2) are commonly used for this purpose to keep Node.js applications running continuously and restart them on failure. For example, npm install forever -g installs forever globally, and forever start app.js can start your server.

Although it is no longer supported, Node.js once included a domain module that tried to assist with exception localisation in asynchronous programs.

Errors and Promises

Promises behave differently from synchronous or callback-driven programs when it comes to handling faults. An error causes a promise to be rejected, and the .catch() method makes anything rejected inside a promise available.

Code Example:

const p = new Promise(function (resolve, reject) {
    reject(new Error('Oops'));
});
// anything that is `reject`ed inside a promise will be available through catch
// while a promise is rejected, `.then` will not be called
p
    .then(() => {
        console.log("won't be called");
    })
    .catch(e => {
        console.log(e.message); // output: Oops
    })
    // once the error is caught, execution flow resumes
    .then(() => {
        console.log('hello!'); // output: hello!
    });

Code Output:

Oops
hello!

JavaScript immediately jumps to the catch() method that is closest to any promise in a chain if it is not fulfilled, or rejected. Comparing this to nested callbacks (callback hell), a more efficient error handling technique is made possible.

Debugging was challenging prior to Node.js 8 because promises that contained errors but were not explicitly caught could be silently eaten. However, Node.js 8 deprecates this behaviour in favour of stopping the Node process in the event of such an error. This modification encourages developers to adopt appropriate error handling techniques or to always include a catch clause.

Index