75

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?

Marcos Casagrande
  • 37,983
  • 8
  • 84
  • 98
  • 1
    Here's a good article I found when i was googling about it: https://strongloop.com/strongblog/async-error-handling-expressjs-es7-promises-generators/ – komron Feb 09 '20 at 00:23

6 Answers6

125

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 express@5.x.x

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

Marcos Casagrande
  • 37,983
  • 8
  • 84
  • 98
  • 9
    Also worth mentioning that the the framework [Koa](https://koajs.com/) (supposedly built by people who first worked on Express) has more built-in conveniences for async operations in request handlers and the kind of Promise awareness and error handling the OP was asking about. – jfriend00 Jul 17 '18 at 23:32
  • 11
    Typescripted: `export const asyncHandler = (fn: RequestHandler) => (req: Request, res: Response, next: NextFunction) => Promise.resolve(fn(req, res, next)).catch(next)`. Thanks! – Kyll Jan 17 '19 at 12:39
  • 3
    More info on the `express@5.x.x` support: https://expressjs.com/en/guide/error-handling.html. I've tried `5.0.0-alpha.8`, and it works exactly as described: `route handlers and middleware that return a Promise will call next(value) automatically when they reject or throw an error.`. – kmui2 Mar 31 '21 at 06:24
  • Why can't asyncHandler be an async function itself too? – Normal Jun 22 '22 at 07:04
  • I'm a bit confused, Can you explain why you used `Promise.resolve(fn(...)).catch()` instead of simply `fn(...).catch()` ? – cak3_lover Aug 30 '23 at 19:26
44

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');
ama
  • 1,525
  • 1
  • 16
  • 34
16

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.

8

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

Sebastien Horin
  • 10,803
  • 4
  • 52
  • 54
7

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 });
});
Sunny Sultan
  • 1,090
  • 15
  • 20
  • 1
    But this sets a common 422 error code for all responses. What's wrong if I don't set up an error middleware and deal with all the errors inside the handler code, returning the wanted JSON and status? – ankush981 Jan 29 '22 at 13:06
4

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.