处理Express异步中间件中的错误

Mar*_*nde 5 javascript node.js express es6-promise

我有一个asyncExpress中间件,因为我想在其中使用中间件await来清理我的代码。

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);
Run Code Online (Sandbox Code Playgroud)

问题是当它拒绝时,它不会进入我的错误中间件,但是如果我删除了async关键字,并且throw在中间件内部,它就会去。

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

所以我得到的UnhandledPromiseRejectionWarning不是输入我的错误处理中间件,而是如何让错误冒泡并表达处理它?

Adr*_*ian 25

好吧,我找到了这个 - https://github.com/davidbanham/express-async-errors/,然后需要脚本,你很高兴

const express = require('express');
require('express-async-errors');
Run Code Online (Sandbox Code Playgroud)

  • 这个模块还是很有用的。如下所述,Express v5 将添加对异步中间件的支持,但截至今天还没有发布 v5。 (6认同)
  • 如果您使用路由器,则只需在每个路由器文件的开头需要它 (5认同)

Mar*_*nde 12

问题是当它拒绝时,它不会进入我的错误中间件,但是如果我删除了async关键字并扔进了中间件中,它就会去。

express 目前不支持诺言,将来的版本可能会支持 express@5.x.x

因此,当您传递中间件函数时,express将在一个try/catch块内调用它。

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);
  }
};
Run Code Online (Sandbox Code Playgroud)

问题在于,该函数try/catch不会捕获函数Promise外部的拒绝,async并且由于express没有在中间件返回的.catch函数中添加处理程序Promise,因此您会得到UnhandledPromiseRejectionWarning


最简单的方法是try/catch在中间件中添加,然后调用next(err)

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

但是,如果您有很多async中间件,则可能有些重复。

由于我喜欢中间件尽可能干净,并且通常让错误冒出来,因此我在async中间件周围使用了包装器,next(err)如果承诺被拒绝,它将调用,到达明确的错误处理程序并避免UnhandledPromiseRejectionWarning

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

module.exports = asyncHandler;
Run Code Online (Sandbox Code Playgroud)

现在您可以这样称呼它:

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
Run Code Online (Sandbox Code Playgroud)

还有一些可以使用的软件包

  • 打字稿:`export const asyncHandler = (fn: RequestHandler) => (req: Request, res: Response, next: NextFunction) => Promise.resolve(fn(req, res, next)).catch(next)`。谢谢! (5认同)
  • 还值得一提的是[Koa](https://koajs.com/)框架(据说是由最早使用Express的人构建的)为请求处理程序中的异步操作提供了更多内置的便利,并且具有Promise意识和处理OP询问时出错。 (4认同)
  • 有关“express@5.xx”支持的更多信息:https://expressjs.com/en/guide/error-handling.html。我已经尝试过“5.0.0-alpha.8”,它的工作原理与所描述的完全一样:“返回 Promise 的路由处理程序和中间件在拒绝或抛出错误时将自动调用 next(value)”。 (4认同)

Mik*_*rko 9

用 asyncHandler 回答是好的和有用的,但是在每个路由中编写这个包装器仍然不舒服。我建议改进它:

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
}
Run Code Online (Sandbox Code Playgroud)

现在我们可以这样做:

const router = toAsyncRouter(express().Router())
router.get('/', someAsyncController)
Run Code Online (Sandbox Code Playgroud)

等等。

分钟前添加了一个 npm 模块async-express-decorator


Seb*_*rin 8

Express 5 现在处理异步承诺:

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

从 Express 5 开始,返回 Promise 的路由处理程序和中间件将在拒绝或抛出错误时自动调用 next(value)。例如

  • 快车5什么时候发布? (3认同)

Sun*_*tan 7

您需要使用 try-catch 并在 catch 部分中将错误传递到 next() 参数中,如下所示 -

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

显然,将此快速中间件放在您的 index.js 文件中。

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

  • 但这为所有响应设置了一个通用的 422 错误代码。如果我不设置错误中间件并处理处理程序代码内的所有错误,返回所需的 JSON 和状态,会出现什么问题? (2认同)