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

Express and async/await: shall I wrap the lot with try/catch?

提问人:Merc 提问时间:4/5/2018 最后编辑:Himanshu BansalMerc 更新时间:5/15/2018 访问量:5249

问:

我有以下代码:

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('')
})

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

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

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

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

节点 .js 表示 异步等待 尝试捕获

评论

0赞 Himanshu Bansal 4/5/2018
请参考此处了解中间件 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!') });
2赞 Merc 5/15/2018
错误中间件不会被调用,因为错误没有被捕获到...

答:

13赞 Cisco 4/5/2018 #1

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

您有两种选择:

  1. 将所有内容包装在一个try-catch
  2. 编写自己的错误处理中间件,如此处的文档所示。

调用会将 和 传递给堆栈中的下一个中间件。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

请注意,此处导入并使用了上面定义的控制器。此外,您会看到控制器操作用 包装。这样就避免了一直写入,任何错误都会被中间件捕获。resetPasswordcatchErrorstry-catchcatchErrors

最后,将它们全部连接到您的主文件中: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

评论

0赞 Merc 5/15/2018
我不确定我是否理解“选项 2”:我已经有错误处理中间件。但是,除非我在那里有 try-catch,否则它不会被调用。所以。。。我想我必须同时拥有 (1) 和 (2),这样我才能做对吗?try { ... } catch (e) { next(err) }
0赞 Cisco 5/15/2018
您可以这样做或将函数包装在 因为函数总是返回 .请参阅带有示例的更新答案。catchasyncPromise
0赞 denski 12/2/2022
您能否将声明添加到.它会捕获异步错误吗?asyncexports.catchErrors = action => (req, res, next) => action(req, res).catch(next)
1赞 Gustavo Topete 4/5/2018 #2

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

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

评论

0赞 Gustavo Topete 5/15/2018
我明白,但承诺是一个(重要的)ES 标准,我不知道是否可以在没有 和 方法的情况下使用它们。您可以尝试其他类似 Observables(还不是 ES 标准)之类的东西,或者停止使用 JS/Node,在我看来这不是一个选择then()catch
0赞 user2347763 4/5/2018 #3

正如其他人所回应的那样,由于您使用的是 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")
})