Express 和 async/await:我应该用 try/catch 包装吗?

Posted

技术标签:

【中文标题】Express 和 async/await:我应该用 try/catch 包装吗?【英文标题】:Express and async/await: shall I wrap the lot with try/catch? 【发布时间】:2018-09-14 18:58:11 【问题描述】:

我有以下代码:

app.post('/routes/passwordReset/:userId', async (req, res, next) => 
  var userId = req.params.userId

  let record = (await vars.connection.queryP('SELECT * FROM contacts WHERE id = ?', userId))[0]

  if (!record) 
    res.status(404).send('')
    return
  

  // Calculate token and expiry, update database
  var token = require('crypto').randomBytes(48).toString('base64').replace(/[^a-zA-Z0-9]/g, '').substr(0, 28)
  var now = new Date()
  var inOneHour = new Date(now.getTime() + (1 * 1000 * 60 * 60))
  await vars.connection.queryP('UPDATE contacts SET recoverToken = ?, recoverTokenExpiry = ? WHERE id = ?', [ token, inOneHour, userId ])
  res.status(200).send('')
)

如果我创建了一个人为错误(例如,recoverTokenExpiry 的字段人为太短)我最终会得到:

[[12:19:55.649]] [错误](节点:11919) UnhandledPromiseRejectionWarning:未处理的承诺拒绝 (拒绝 id:4):错误:ER_DATA_TOO_LONG:数据对于列来说太长 第 1 行的“recoverToken”

我不知何故认为 Express 会在中间件周围进行尝试/捕获,并在抛出错误时调用 next(err)。也许不是这样?

那么,我是否应该用 try/catch(e) 包裹每条路由,如果出现错误则执行 next(e)?

【问题讨论】:

请参考这里的中间件 try/catch expressjs.com/en/guide/error-handling.html app.use(function (err, req, res, next) console.error(err.stack) res.status(500).send('Something broke!') ); 之类的东西 错误中间件没有被调用,因为错误没有被捕获... 【参考方案1】:

Express 不会为您做任何事情。这是非常简单的骨头。

你有两个选择:

    将所有内容包装在try-catch 编写您自己的错误处理中间件,如文档here 中所示。

调用next() 会将reqres 传递给堆栈中的下一个中间件。

调用next(err),或者给它一个Exception对象,将调用堆栈中的任何错误中间件。


在自己的文件中定义错误处理程序:

// error-middleware.js

/**
 * Handler to catch `async` operation errors.
 * Reduces having to write `try-catch` all the time.
 */
exports.catchErrors = action => (req, res, next) => action(req, res).catch(next)

/**
 * Show useful information to client in development.
 */
exports.devErrorHandler = (err, req, res, next) => 
  err.stack = err.stack || ''
  const status = err.status || 500
  const error =  message: err.message 
  res.status(status)
  res.json( status, error )

然后将您的密码重置逻辑从app.post 中移出并移到它自己的专用函数中:

// password-controller.js

exports.resetPassword = async (req, res) => 
  // do work
  // ...

这使得编写单元测试和关注点的清晰分离变得更加容易。

接下来创建您的密码重置路线:

// password-reset-routes.js

const express = require('express')
const router = express.Router()

const passwordController = require('./password-controller')
const  catchErrors  = require('./error-middleware')

router.post('/routes/passwordReset/:userId', catchErrors(passwordController.resetPassword))

module.exports = router

请注意,上面定义的控制器是在这里导入和使用的。此外,您还看到控制器操作resetPasswordcatchErrors 包裹。这避免了必须一直写try-catch,任何错误都会被catchErrors中间件捕获。

最后,将它们全部连接到您的主 app.js 文件中:

// app.js

const express = require('express')
const  devErrorHandler  = require('./error-middleware')
const passwordResetRoutes = require('./password-reset-routes.js')
const app = express()

app.use(devErrorHandler)
app.use(passwordResetRoutes)

module.exports = app

【讨论】:

我不确定我是否理解“选项 2”:我已经有错误处理中间件。但是,除非我在那里有 try-catch,否则它不会被调用。所以...我假设我必须同时拥有 (1) 和 (2),这样我才能做到 try ... catch (e) next(err) 对吗? 可以这样做或将函数包装在catch 中,因为async 函数总是返回Promise。通过示例查看更新的答案。【参考方案2】:

正如其他人所回应的那样,由于您使用的是await,因此您可以将promise拒绝处理为

let record = await vars.connection.queryP('SELECT * FROM contacts WHERE id = ?', userId))[0].then(function () 
     console.log("Promise Resolved");
).catch((error)=>
    console.log("Promise rejected")
)

【讨论】:

【参考方案3】:

好吧,当您使用 await 关键字时,这意味着执行流程不再是异步的,这就是它失败的原因,是的,您必须每次都使用 try-catch 块。为避免这种情况,您可以改为遵循异步流程并分别使用 then() 和 catch(),因此您可以执行以下操作:

...
.then((status)=>
  res.status(200).send('')
)
.catch((err)=>
  next(err);
);
...

【讨论】:

我理解,但是 promise 是一个(重要的)ES 标准,我不知道是否可以在没有 then()catch 方法的情况下使用它们。你可以试试 Observables 之类的东西(还不是 ES 标准),或者干脆停止使用 JS/Node,在我看来这不是一个选项

以上是关于Express 和 async/await:我应该用 try/catch 包装吗?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 async/await 在 Express 中完成这项工作

在 express (async/await) 中使用一个 mongoose 查询的输出作为另一个查询的输入

Async/await mvc express 使用 .catch() 处理错误的问题

使用 async/await 时在 Express 中捕获未处理的异常

如何使用 async/await 查询 MongoDB?

为啥 async / await 函数运行两次?