express-async-handler icon indicating copy to clipboard operation
express-async-handler copied to clipboard

`asyncUtilWrap` expects the `next` argument to be the last, which is incorrect when there are additional arguments

Open robertbullen opened this issue 6 years ago • 9 comments

The unit test 'should provide additional arguments to the middleware' implies that arguments following the next callback are allowed. But asyncUtilWrap always looks for next as the last argument. Thus, in a scenario where additional arguments are provided and an error is thrown, next will not be properly called with the error.

robertbullen avatar Feb 23 '19 17:02 robertbullen

Anyone?

robertbullen avatar Mar 12 '19 14:03 robertbullen

Why would your express middleware arguments ever have anything after the next? The only two middleware signatures that express supports are:

  • (req, res, next)
  • (err, req, res, next)

Where are you even using this library?

MunifTanjim avatar Mar 24 '19 21:03 MunifTanjim

I personally am not using a middleware signature with arguments after next(). But your question is very relevant.

There two pieces of evidence that suggest it is a supported scenario:

  1. There are unit tests for it in this project.
  2. Express's app.param() function takes a callback accepting an argument after next() (example copied from that API's documentation):
app.param('user', function(req, res, next, id) {

  // try to get the user details from the User model and attach it to the request object
  User.find(id, function(err, user) {
    if (err) {
      next(err);
    } else if (user) {
      req.user = user;
      next();
    } else {
      next(new Error('failed to load user'));
    }
  });
});

robertbullen avatar Mar 26 '19 15:03 robertbullen

As far as I know, express-async-handler is only for using with express middlewares.

There are unit tests for it in this project.

I noticed that too :thinking: It was added by one of the contributors, but I don't think it was intentional (a208f5f51c8ffde84ebbd050b6e5469d5f8d9097)

Express's app.param() function takes a callback accepting an argument

Well, we better not throw in that callback :sweat_smile:

MunifTanjim avatar Mar 26 '19 19:03 MunifTanjim

Why would your express middleware arguments ever have anything after the next? The only two middleware signatures that express supports are:

  • (req, res, next)
  • (err, req, res, next)

Where are you even using this library?

I've just faced this issue when using this function for middleware in a parameter handler. The parameter name is the last argument then.

artursudnik avatar Mar 18 '20 21:03 artursudnik

@robertbullen here's a fix to correctly get the next argument:

exports.handleAsyncErrors = fn => function AsyncErrorsWrapper(...args) {
  // next is always the last function of the arguments passed to an express middleware or view
  const getNext = args => [...args].reverse().find(arg => typeof arg === 'function')
  const next = getNext(args)
  return fn(...args).catch(next)
}

agconti avatar Jul 10 '21 14:07 agconti

@robertbullen @artursudnik Hi from 2021! :-)

If there are extra parameters, I wonder how Express distinguishes e.g. (err, req, res, next) case from (req, res, next, myParam) case. It internally looks at handler.length and, if it's 4, then it realizes it's an error-catching handler and calls them. But with the parameters, how does it work?

dko-slapdash avatar Oct 16 '21 02:10 dko-slapdash

BTW, it's worth assigning asyncUtilWrap.length to fn.length (via Object.defineProperty()) to forward the number of arguments to Express. It would then allow to use express-async-handler for error handlers too.

dko-slapdash avatar Oct 16 '21 02:10 dko-slapdash

@robertbullen @artursudnik Hi from 2021! :-)

If there are extra parameters, I wonder how Express distinguishes e.g. (err, req, res, next) case from (req, res, next, myParam) case. It internally looks at handler.length and, if it's 4, then it realizes it's an error-catching handler and calls them. But with the parameters, how does it work?

Apparently, it does not have to. (req, res, next, id) is registered with app.param not app.use.

artursudnik avatar Oct 17 '21 09:10 artursudnik