Unhandled rejections in Express applications

node.jsExpressEs6 Promise

node.js Problem Overview


I have a lot of ES6 promise based code running inside my express app. If there is an error that is never caught I'm using the following code to deal with it:

process.on('unhandledRejection', function(reason, p) {
  console.log("Unhandled Rejection:", reason.stack);
  process.exit(1);
});

This works fine for debugging purposes.

In production however I would like to trigger the 500 error handler, to show the user the standard "Something went wrong" page. I have this catch all error handler that currently works for other exceptions:

app.use(function(error, req, res, next) {
  res.status(500);
  res.render('500');
});

Putting the unhandledRejection inside a middleware does not work as it's async and offen results in a Error: Can't render headers after they are sent to the client.

How would I go about rendering the 500 page on an unhandledRejection?

node.js Solutions


Solution 1 - node.js

> Putting the unhandledRejection inside a middleware...often results in a Error: Can't render headers after they are sent to the client.

Make a slight change to your error handler:

// production error handler
const HTTP_SERVER_ERROR = 500;
app.use(function(err, req, res, next) {
  if (res.headersSent) {
    return next(err);
  }

  return res.status(err.status || HTTP_SERVER_ERROR).render('500');
});

From the ExpressJS Documentation:

> Express comes with an in-built error handler, which takes care of any errors that might be encountered in the app. This default error-handling middleware is added at the end of the middleware stack. > > If you pass an error to next() and you do not handle it in an error handler, it will be handled by the built-in error handler - the error will be written to the client with the stack trace. The stack trace is not included in the production environment. > >> Set the environment variable NODE_ENV to “production”, to run the app in production mode. > > If you call next() with an error after you have started writing the response, for instance if you encounter an error while streaming the response to the client, Express’ default error handler will close the connection and make the request be considered failed. > > So when you add a custom error handler you will want to delegate to the default error handling mechanisms in express, when the headers have already been sent to the client.

Solution 2 - node.js

I'm using next argument as a catch callback(aka errback) to forward any unhandled rejection to express error handler:

app.get('/foo', function (req, res, next) {
  somePromise
    .then(function (result) {
      res.send(result);
    })
    .catch(next); // <----- NOTICE!
}

or shorter form:

app.get('/foo', function (req, res, next) {
  somePromise
    .then(function (result) {
       res.send(result); 
    }, next); // <----- NOTICE!
}

and then we could emit meaningful error response with err argument in express error handler.

for example,

app.use(function (err, req, res, /*unused*/ next) {
  // bookshelf.js model not found error
  if (err.name === 'CustomError' && err.message === 'EmptyResponse') {
    return res.status(404).send('Not Found');
  }
  // ... more error cases...
  return res.status(500).send('Unknown Error');
});

IMHO, global unhandledRejection event is not the ultimate answer.

for example, this is prone to memory leak:

app.use(function (req, res, next) {
  process.on('unhandledRejection', function(reason, p) {
    console.log("Unhandled Rejection:", reason.stack);
    res.status(500).send('Unknown Error');
    //or next(reason);
  });
});

but this is TOO heavy:

app.use(function (req, res, next) {
  var l = process.once('unhandledRejection', function(reason, p) {
    console.log("Unhandled Rejection:", reason.stack);
    res.status(500).send('Unknown Error');
    //next(reason);
  });
  next();
  process.removeEventLister('unhandledRejection', l);
});

IMHO, expressjs needs better support for Promise.

Solution 3 - node.js

express-promise-router was made to solve exactly this problem. It allows your routes to return promises, and will call next(err) if such a promise is rejected with an error.

Solution 4 - node.js

By default, if your request is async function that throws an error, express doesn't pass errors to the middleware. Instead process.on('unhandledRejection', callback) is called, and the request will block.

The library express-async-errors was created to solve these errors.

You need to add require('express-async-errors'); to your code and the library will make sure that all your functions come to the handlers. Even if it's an unhandled rejection.

Solution 5 - node.js

Assuming that you are using Express and some promise-based code like so:

readFile() .then(readAnotherFile) .then(doSomethingElse) .then(...)

Add a .catch(next) to the end of your promise chain and Express's middleware will successfully handle both synchronous/asynchronous code using your production error handler.

Here's a great article that goes in-depth to what you are looking for: https://strongloop.com/strongblog/async-error-handling-expressjs-es7-promises-generators/

Solution 6 - node.js

I was looking for a clean way to handle it, Express 5 now handle async promises by design:

> Starting with Express 5, route handlers and middleware that return a > Promise will call next(value) automatically when they reject or throw > an error. For example

https://expressjs.com/en/guide/error-handling.html

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDanielView Question on Stackoverflow
Solution 1 - node.jsJared DykstraView Answer on Stackoverflow
Solution 2 - node.jsioloView Answer on Stackoverflow
Solution 3 - node.jsThomasView Answer on Stackoverflow
Solution 4 - node.jsDavid WeinbergView Answer on Stackoverflow
Solution 5 - node.jsRahat MahbubView Answer on Stackoverflow
Solution 6 - node.jsSebastien HorinView Answer on Stackoverflow