Andrew.

Error handling in Express with promises, generators, and async/await

tldr; Callbacks have a lousy error-handling story. Promises are better. Marry the built-in error handling in Express with promises and significantly lower the chances of an uncaught exception. Promises are native ES6, can be used with generators, and ESNext proposals like async/await through compilers like Babel.

This article focuses on effective ways to capture and handle errors using error-handling middleware in Express1. The article also includes a sample repository of these concepts on GitHub.

First, let’s look at what Express handles out of the box and then we will look at using promises, promise generators and async/await to simplify things further.

Express has built-in synchronous handling

By default, Express will catch any exception thrown within the initial synchronous execution of a route and pass it along to the next error-handling middleware:

Still, this isn’t bulletproof. There are two problems with this approach:

You must explicitly handle everyerror argument.

Implicit exceptions aren’t handled (like trying to access a property that isn’t available on the data object).

Asynchronous error propagation with promises

Promises handle any exception (explicit and implicit) within asynchronous code blocks (inside then) like Express does for us in synchronous code blocks. Just add .catch(next) to the end of promise chains:

Now, to run this code, you will need the Babel JavaScript compiler. There are many ways to use Babel with Node, but to keep things simple, install the babel-node command by running:

npm install babel-cli -g

Then run your app using:

babel-node --stage 0 myapp.js

Bonus: Since this code compiles to ES5, you can use this solution with older versions of Node.

Throw me a party!

With error handling covered both synchronously and asynchronously you can develop Express code differently. Mainly, DO use throw. The intent of throw is clear. If you use throw it will bypass execution until it hits a catch. In other words, it will behave just like throw in synchronous code. You can use throw and try/catch meaningfully again with promises, promise generators, and async/await:

Caveats

There are two caveats with this approach:

You must have all your asynchronous code return promises (except emitters). Raw callbacks simply don’t have the facilities for this to work. This is getting easier as promises are legit now in ES6. If a particular library does not return promises, it’s trivial to convert using a helper function like Bluebird.promisifyAll.

Event emitters (like streams) can still cause uncaught exceptions. So make sure you are handling the error event properly.