Handling unhandled Promise rejections, avoiding application crash

; Date: March 1, 2018

Tags: Node.JS »»»» JavaScript

With Promises and async functions, errors are automatically caught and are turned into a Promise rejection. What happens if nothing inspects the rejected Promise? Turns out that, currently, Node.js detects the unhandled Promise rejection and it prints a warning. In the warning, it says the behavior of catching unhandled Promise rejections is deprecated and will be returned, and instead the application will crash. Therefore it's worth our while to figure out what to do with unhandled Promise rejections.

Let's start with what we mean by this. Suppose buried inside your application lies an async function that sometimes throws an error, but you neglected to catch the error. I don't have an example handy, but can concoct an artificial example.

var NotesModule;
async function model() {
    if (NotesModule) return NotesModule;
    NotesModule = require(`../models/notes-${process.env.NOTES_MODEL}`);
    return undefined; // NotesModule;
}

This function is loading a module that will be used by other code. It doesn't entirely matter what that other code is, just that returning undefined from this function is bad news. That other code will then throw an error because of the undefined value, and that error isn't getting caught in the application.

As a result we get this series of messages:

(node:32263) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'keylist' of undefined
    at Module.keylist (file:///home/david/nodewebdev/node-web-development-code-4th-edition/chap07/notes/models/notes.mjs:14:58)
    at <anonymous>
(node:32263) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:32263) [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.

The message Cannot read property 'keylist' of undefined is the other code throwing its error. But the error isn't caught and Node.js instead says - in effect - woah, I detected an unhandled rejected Promise, and in the future you won't be getting this nice message but instead you'll get a crashed process.

The solution is actually fairly simple. The most comprehensive solution is to handle every error path with appropriate code. But of course we're human and we slip up from time to time. I'm sure I've made at least one mistake in my life.

The workaround that prevents a crash is to add this code to your application:

process.on('unhandledRejection', (reason, p) => {
  error(`Unhandled Rejection at: ${util.inspect(p)} reason: ${reason}`);
});

The process object emits several events, and this new one covers unhandled Promise rejection. For documentation see: (nodejs.org) https://nodejs.org/api/process.html#process_event_unhandledrejection

The output looks like so:

  notes:error Unhandled Rejection at: Promise {
  notes:error   <rejected> TypeError: Cannot read property 'keylist' of undefined
  notes:error     at Module.keylist (file:///home/david/nodewebdev/node-web-development-code-4th-edition/chap07/notes/models/notes.mjs:14:58)
  notes:error     at <anonymous> } reason: TypeError: Cannot read property 'keylist' of undefined +6s

Or, in another instance:

  notes:error Unhandled Rejection at: Promise {
  notes:error   <rejected> TypeError: model(...).keylist is not a function
  notes:error     at Module.keylist (file:///home/david/nodewebdev/node-web-development-code-4th-edition/chap07/notes/models/notes.mjs:40:44)
  notes:error     at router.get (file:///home/david/nodewebdev/node-web-development-code-4th-edition/chap07/notes/routes/index.mjs:9:29)
  notes:error     at Layer.handle [as handle_request] (/home/david/nodewebdev/node-web-development-code-4th-edition/chap07/notes/node_modules/express/lib/router/layer.js:95:5)
  notes:error     at next (/home/david/nodewebdev/node-web-development-code-4th-edition/chap07/notes/node_modules/express/lib/router/route.js:137:13)
  notes:error     at Route.dispatch (/home/david/nodewebdev/node-web-development-code-4th-edition/chap07/notes/node_modules/express/lib/router/route.js:112:3)
  notes:error     at Layer.handle [as handle_request] (/home/david/nodewebdev/node-web-development-code-4th-edition/chap07/notes/node_modules/express/lib/router/layer.js:95:5)
  notes:error     at /home/david/nodewebdev/node-web-development-code-4th-edition/chap07/notes/node_modules/express/lib/router/index.js:281:22
  notes:error     at Function.process_params (/home/david/nodewebdev/node-web-development-code-4th-edition/chap07/notes/node_modules/express/lib/router/index.js:335:12)
  notes:error     at next (/home/david/nodewebdev/node-web-development-code-4th-edition/chap07/notes/node_modules/express/lib/router/index.js:275:10)
  notes:error     at Function.handle (/home/david/nodewebdev/node-web-development-code-4th-edition/chap07/notes/node_modules/express/lib/router/index.js:174:3) } reason: TypeError: model(...).keylist is not a function +3s

This is very useful output - since it tells us exactly where the issue occurred, and the precise problem. Plus, the application doesn't crash.