This simple snippet of Javascript has saved me untold amounts of headache and heartache since I first started adding it to my Node.js projects:


process.on('unhandledRejection', err => {
    console.log('Unhandled rejection:', err);
});

Let’s dig into the sorry state of unhandled promise rejections in Node.js and find out why this simple piece of code can be such a life-saver in sufficiently large projects.

An Unresolved Promise

Imagine we have the following code buried deep within our Node.js application:


const foo = () => get('foo').then(res => res.body);
const bar = () => get('bar').then(res => res.body);

Our foo function makes a call to the asynchronous get function, which returns a Promise. After the promise resolves, we return the result’s body. Our bar function makes a similar call to get and also returns the result’s body.

Running our application results in the following incredibly unhelpful error message:


(node:5175) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): TypeError: Cannot read property 'body' of undefined
(node:5175) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

So somewhere in my codebase, the body field is trying to be accessed on undefined. Where? Who knows. What’s the context? No telling! How do we track it down? I don’t know, maybe a Ouija board?

Tools for Handling Rejection

Thankfully, Node.js ships with the tools required to remedy this situation. Much like the more well-known "uncaughtException" process event, Node.js applications can listen for "unhandledRejection" events at the process level. These events are fired any time a rejection bubbles to the top of a promise chain without encountering a catch callback.

Let’s add an "unhandledRejection" listener to our application. We’ll keep things simple and log the error reported by the process:


process.on('unhandledRejection', err => {
    console.log('Unhandled rejection:', err);
});

Let’s try running our application again:


Unhandled rejection: TypeError: Cannot read property 'body' of undefined
    at get.then.res (/Users/pcorey/test/promise.js:11:46)
    at <anonymous>
    at process._tickCallback (internal/process/next_tick.js:188:7)
    at Function.Module.runMain (module.js:678:11)
    at startup (bootstrap_node.js:187:16)
    at bootstrap_node.js:608:3

Sweet clarity!

Our "unhandledRejection " event listener is report on our unhandled rejection, and now we’re given a stack trace that pinpoints the source of the error. We can clearly see that the unhandled rejection is occurring on line 11 of our example application:


const bar = () => get('bar').then(res => res.body);

It looks like our asynchronous call to get('bar') is returning undefined, and our res.body expression is throwing an exception. Given this information, we can easily debug the situation and come up with a solution.

The Future Can’t Come Soon Enough

As mentioned in our earlier error:

In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

As far as I’m concerned, this is the expected and preferred behavior. Keeping an application alive after an unhandled exception has bubbled up to the event loop, even within the context of a promise, should result in the process being killed and a proper stack trace being logged.

The future can’t come soon enough.