You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Express与async/await:是否需为所有代码包裹try/catch?

在Express中使用async/await时是否需要包裹try/catch?

先看你提供的代码片段:

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
  }
  // 计算令牌与过期时间,更新数据库
  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))
  // 省略后续更新数据库的代码
})

答案是必须要处理异步操作的异常,要么用try/catch直接包裹,要么把错误传递给Express的全局错误处理中间件——原因很简单:

当你用await调用一个异步函数(比如你的vars.connection.queryP)时,如果这个操作失败抛出错误,await会把这个错误转化为Promise的reject状态。而async函数本身会返回这个rejected的Promise,如果没有任何代码捕获它,Express会把这个未处理的Promise rejection转化为一个模糊的500错误,甚至在旧版Node.js里可能触发进程警告,严重的话还会导致进程退出。

给你两种常用的处理方式:

1. 直接用try/catch包裹代码块

这是最直观的方式,把所有可能出错的异步操作放在try块里,在catch里处理错误——你可以直接返回用户友好的响应,或者调用next(err)把错误交给全局中间件统一处理:

app.post('/routes/passwordReset/:userId', async (req, res, next) => {
  try {
    const userId = req.params.userId
    const record = (await vars.connection.queryP('SELECT * FROM contacts WHERE id = ?', userId))[0]
    
    if (!record) {
      res.status(404).send('用户不存在')
      return
    }

    // 生成重置令牌
    const token = require('crypto').randomBytes(48)
      .toString('base64')
      .replace(/[^a-zA-Z0-9]/g, '')
      .substr(0, 28)
    const now = new Date()
    const inOneHour = new Date(now.getTime() + 1000 * 60 * 60)

    // 执行数据库更新
    await vars.connection.queryP(
      'UPDATE contacts SET reset_token = ?, reset_token_expire = ? WHERE id = ?',
      [token, inOneHour, userId]
    )

    res.status(200).send('重置令牌已生成')
  } catch (err) {
    // 这里可以根据错误类型做针对性处理,比如数据库连接错误、SQL语法错误等
    console.error('密码重置出错:', err)
    next(err) // 交给全局错误中间件处理
  }
})

2. 用包装函数简化错误捕获

如果你的项目里有很多async路由,每个都写try/catch会很繁琐。可以写一个通用的包装函数,自动捕获async函数的错误并传递给next:

// 定义一个async处理函数的包装器
const asyncHandler = (fn) => (req, res, next) => {
  Promise.resolve(fn(req, res, next)).catch(next)
}

// 用包装器包裹你的路由处理函数
app.post('/routes/passwordReset/:userId', asyncHandler(async (req, res) => {
  const userId = req.params.userId
  const record = (await vars.connection.queryP('SELECT * FROM contacts WHERE id = ?', userId))[0]
  
  if (!record) {
    res.status(404).send('用户不存在')
    return
  }

  // 后续生成令牌、更新数据库的代码...
  res.status(200).send('重置令牌已生成')
}))

最后别忘了全局错误处理中间件

不管用哪种方式,都建议在你的Express应用末尾配置一个全局错误处理中间件,统一处理所有传递过来的错误:

app.use((err, req, res, next) => {
  console.error('全局错误捕获:', err.stack)
  // 可以根据环境返回不同的响应,比如开发环境返回详细错误,生产环境返回通用提示
  const statusCode = err.statusCode || 500
  res.status(statusCode).send(process.env.NODE_ENV === 'production' ? '服务器内部错误' : err.message)
})

内容的提问来源于stack exchange,提问作者Merc

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 07:58:07