Handling errors in express async middleware

Javascriptnode.jsExpressEs6 Promise

Javascript Problem Overview


I have an async middleware in express, because I want to use await inside it, to clean up my code.

const express = require('express');
const app = express();

app.use(async(req, res, next) => {
	await authenticate(req);
	next();
});

app.get('/route', async(req, res) => {
	const result = await request('http://example.com');
	res.end(result);
});

app.use((err, req, res, next) => {
	
	console.error(err);

	res
		.status(500)
		.end('error');
})

app.listen(8080);

The problem is that when it rejects, it doesn't go to my error middleware, but if I remove the async keyword and throw inside a middleware it does.

app.get('/route', (req, res, next) => {
	throw new Error('Error');
	res.end(result);
});

So I'm getting UnhandledPromiseRejectionWarning instead of entering my error handling middleware, how can I let the error bubble up, and express handle it?

Javascript Solutions


Solution 1 - Javascript

> The problem is that when it rejects, it doesn't go to my error > middleware, but if I remove the async keyword and throw inside a > middleware it does.

express doesn't support promises currently, support may come in the future release of [email protected]

So when you pass a middleware function, express will call it inside a try/catch block.

Layer.prototype.handle_request = function handle(req, res, next) {
  var fn = this.handle;

  if (fn.length > 3) {
    // not a standard request handler
    return next();
  }

  try {
    fn(req, res, next);
  } catch (err) {
    next(err);
  }
};

The problem is that try/catch won't catch a Promise rejection outside of an async function and since express does not add a .catch handler to the Promise returned by your middleware, you get an UnhandledPromiseRejectionWarning.


The easy way, is to add try/catch inside your middleware, and call next(err).

app.get('/route', async(req, res, next) => {
	try {
    	const result = await request('http://example.com');
    	res.end(result);
	} catch(err) {
		next(err);
	}
});

But if you have a lot of async middlewares, it may be a little repetitive.

Since I like my middlewares as clean as possible, and I usually let the errors bubble up, I use a wrapper around async middlewares, that will call next(err) if the promise is rejected, reaching the express error handler and avoiding UnhandledPromiseRejectionWarning

const asyncHandler = fn => (req, res, next) => {
    return Promise
        .resolve(fn(req, res, next))
        .catch(next);
};

module.exports = asyncHandler;

Now you can call it like this:

app.use(asyncHandler(async(req, res, next) => {
    await authenticate(req);
    next();
}));

app.get('/async', asyncHandler(async(req, res) => {
	const result = await request('http://example.com');
	res.end(result);
}));

// Any rejection will go to the error handler

There are also some packages that can be used

Solution 2 - Javascript

Well, I found this - https://github.com/davidbanham/express-async-errors/, then require the script and you are good to go

const express = require('express');
require('express-async-errors');

Solution 3 - Javascript

Answer with asyncHandler is good and usefull, but it is still not comfortable to write this wrapper in every route. I propose to improve it:

const asyncHandler = fn => (req, res, next) => {
    return Promise
        .resolve(fn(req, res, next))
        .catch(next)
}

const methods = [
    'get',
    'post',
    'delete'  // & etc.
]

function toAsyncRouter(router) {
    for (let key in router) {
        if (methods.includes(key)) {
            let method = router[key]
            router[key] = (path, ...callbacks) => method.call(router, path, ...callbacks.map(cb => asyncHandler(cb)))
        }
    }
    return router
}

and now we can do that way:

const router = toAsyncRouter(express().Router())
router.get('/', someAsyncController)

and so one.

Minute ago added a npm module async-express-decorator.

Solution 4 - Javascript

You need to use try-catch and in catch section just pass the error in next() parameter Like this -

async create(req, res, next) {

    try {
      const userProp = req.body;
      const user = new User(userProp)

      const response = await user.save()
      const token = await user.createJWSToken()
      
      res.send({response, token})

    } catch (err){
      next(err)
    }
}

And obviously put this express middleware on your index.js file.

app.use((err, req, res, next) => {
  res.status(422).send({ error: err.message });
});

Solution 5 - Javascript

Express 5 now handle async promises:

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

> 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

Solution 6 - Javascript

You need to callbackify your async handler. If you know the concept of promisify, this is the opposite. Callbackify is built-in in Node.

import util from 'util'

app.use(util.callbackify(async (req, res) => {
  await authenticate(req);
}));

What this does is that it returns a function with a third argument which would be the next function and calls it after the promise has been resolved. If the promise is rejected, the next function will be called with the error as an argument.

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
QuestionMarcos CasagrandeView Question on Stackoverflow
Solution 1 - JavascriptMarcos CasagrandeView Answer on Stackoverflow
Solution 2 - JavascriptamaView Answer on Stackoverflow
Solution 3 - JavascriptMikhail DezhurkoView Answer on Stackoverflow
Solution 4 - JavascriptSunny SultanView Answer on Stackoverflow
Solution 5 - JavascriptSebastien HorinView Answer on Stackoverflow
Solution 6 - JavascriptSakari TuominenView Answer on Stackoverflow