【问题标题】:Handling errors in express async middleware处理快速异步中间件中的错误
【发布时间】:2018-12-25 17:28:36
【问题描述】:

我在 express 中有一个 async 中间件,因为我想在其中使用 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);

问题是,当它拒绝时,它不会转到我的错误中间件,但如果我删除中间件中的 async 关键字和 throw ,它会这样做。

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

所以我得到UnhandledPromiseRejectionWarning,而不是进入我的错误处理中间件,我怎样才能让错误冒出来,并快速处理它?

【问题讨论】:

标签: javascript node.js express es6-promise


【解决方案1】:

Express 5 现在处理异步承诺:

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

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

【讨论】:

    【解决方案2】:

    您需要回调您的异步处理程序。如果你知道 Promisify 的概念,那就相反了。 Callbackify 是 Node 内置的。

    import util from 'util'
    
    app.use(util.callbackify(async (req, res) => {
      await authenticate(req);
    }));
    

    它的作用是返回一个带有第三个参数的函数,该参数将是下一个函数,并在 promise 被解决后调用它。如果 promise 被拒绝,将调用 next 函数并将错误作为参数。

    【讨论】:

      【解决方案3】:

      好吧,我找到了这个 - https://github.com/davidbanham/express-async-errors/,然后需要脚本,你就可以开始了

      const express = require('express');
      require('express-async-errors');
      

      【讨论】:

      • 这个模块还是有用的。如下所述,Express v5 将添加对异步中间件的支持,但截至今天还没有发布 v5。
      • 太糟糕了,它不适用于路由器
      • 你拯救了我的一天。谢谢 :)
      • 如果你使用路由器,你只需要在每个路由器文件的开头都需要它
      【解决方案4】:

      您需要使用 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)
          }
      }
      

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

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

      【讨论】:

      • 但这会为所有响应设置一个通用的 422 错误代码。如果我不设置错误中间件并处理处理程序代码中的所有错误,返回所需的 JSON 和状态,会有什么问题?
      【解决方案5】:

      用 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
      }
      

      现在我们可以这样做了:

      const router = toAsyncRouter(express().Router())
      router.get('/', someAsyncController)
      

      等等。

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

      【讨论】:

        【解决方案6】:

        问题是当它拒绝时,它并没有进入我的错误 中间件,但是如果我删除 async 关键字并放入 中间件。

        express 目前不支持 Promise,可能会在 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);
          }
        };
        

        问题是try/catch 不会在async 函数之外捕获Promise 拒绝,并且由于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);
            }
        });
        

        但是如果你有很多async中间件,可能会有点重复。

        因为我喜欢我的中间件尽可能的干净,而且我通常会让错误冒泡,所以我在async 中间件周围使用了一个包装器,如果承诺被拒绝,它将调用next(err),到达快速错误处理程序和避免UnhandledPromiseRejectionWarning

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

        现在你可以这样称呼它:

        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
        

        还有一些包可以使用

        【讨论】:

        • 另外值得一提的是,框架Koa(据说是由最初在 Express 上工作的人构建的)为请求处理程序中的异步操作以及 Promise 感知和错误处理提供了更多的内置便利OP 在询问。
        • 打字稿:export const asyncHandler = (fn: RequestHandler) => (req: Request, res: Response, next: NextFunction) => Promise.resolve(fn(req, res, next)).catch(next)。谢谢!
        • 有关express@5.x.x 支持的更多信息:expressjs.com/en/guide/error-handling.html。我试过5.0.0-alpha.8,它的工作原理与描述的完全一样:route handlers and middleware that return a Promise will call next(value) automatically when they reject or throw an error.
        猜你喜欢
        • 2018-04-06
        • 2017-05-25
        • 1970-01-01
        • 2020-01-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-01-25
        • 1970-01-01
        相关资源
        最近更新 更多