javascriptnode.jsexpresses6-promise

Handling errors in express async middleware


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?


Solution

  • Update:

    express@5 was just released with support for async routes/middlewares.


    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