3

Suppose I have a route like this:

app.get('/broken', (req, res) => {
  throw new Error('Broken!');
});

This will never send a response to clients.

However, I can add a middleware for all errors:

const errorMiddleware = (error, req, res, next) => {
  if (error) {
    console.error(error);
    return res.status(500)
      .json({
        message: 'Internal server error', 
      });
  }
  next(error);
};

But this will not work for async routes, because they do not throw directly.

For example, this will not work:

app.get('/broken', async (req, res) => {
  throw new Error('Broken!');
});

So I can create a wrapper like this:

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

app.get('/broken', asyncRoute(async (req, res) => {
  throw new Error('Broken!');
}));

But this is a real pain, because now I have to call this function for every route!

What is a better way of handling this?


sdgfsdh
  • 33,689
  • 26
  • 132
  • 245
  • What benefit do you think you're getting from making the route callback `async`? Express doesn't use the promise, so... Is it purely to get `await` semantics within it? If so, your wrapper is the right way to do that. – T.J. Crowder Apr 26 '18 at 15:56
  • 1
    (You might look at [Koa](http://koajs.com).) – T.J. Crowder Apr 26 '18 at 15:57
  • Well some of my routes require multiple `async` calls (e.g. databases, APIs, bcrypt) and therefore are easier to write in an `async` context. – sdgfsdh Apr 26 '18 at 15:57
  • Yeah, that's what I thought. So your wrapper is the way to do that. You could do `const appGet = handler => app.get(asyncRoute(handler));` once, and then use `appGet(async (req, rest) { ... });` as necessary. – T.J. Crowder Apr 26 '18 at 15:59
  • Side note: Your `asyncRoute` is creating a promise for no reason. Instead: `const asyncRoute = f => f(req, res, next).catch(next);` – T.J. Crowder Apr 26 '18 at 16:01
  • 1
    @T.J.Crowder If I do not pass a promise (e.g. `123`) then I want a promise to be forced (e.g. `Promise.resolve(123)`). Note that `Promise.resolve(Promise.resolve(x))` is flattened to `Promise.resolve(x)`. – sdgfsdh Apr 26 '18 at 16:03
  • @T.J.Crowder Yes, Koa looks better but it is too late for my current project! – sdgfsdh Apr 26 '18 at 16:04
  • 1
    Okay. I didn't see any reason for passing a non-promise into something called `asyncRoute`, so I didn't realize you wanted to handle that. :-) – T.J. Crowder Apr 26 '18 at 16:04

1 Answers1

2

Fundamentally, you don't want to directly pass an async function into Express's app.get, because app.get doesn't handle the promise the function returns. So you'll need to wrap those async handlers (as you're doing).

You can avoid having to do it every time by giving yourself a utility method at the top of the module:

const appGet = handler => app.get(asyncRoute(handler));

then use it instead of app.get:

appGet('/broken', async (req, res) => {
  throw new Error('Broken!');
});

At some point (probably not right now), you might want to look at Koa.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875