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
相关产品推荐
相关产品推荐

