如何优雅改写Mongoose多异步回调的Node.js密码修改函数?
如何优化Node.js/Mongoose密码修改函数的回调地狱和重复错误处理?
我现在有一个可用的密码修改函数,但它存在回调地狱和错误处理代码重复的问题。想知道能不能通过done参数或者链式调用优化,是否需要每个回调都处理错误,以及怎么把代码改得更优雅?
原代码如下:
exports.changePassword = (req, res) => { const { token, password, confirmPassword } = req.body User.findOne({resetPasswordToken: token}, (err, user)=>{ if(!user){ return res.status(400).send({ msg: 'Invalid token or token has been used!' }) } const hash_password = bcrypt.hashSync(password, 10) User.findOneAndUpdate({_id: user._id}, {hash_password}, (err, result)=>{ if(err){ return res.status(400).send({ msg: err }) } User.findOneAndUpdate({_id: user._id}, {resetPasswordToken: ''}, (err, result)=>{ if(err){ return res.status(400).send({ msg: err }) } res.status(200).json({ status: 1, data: 'Your password has been changed.' }) } ) }) }) }
嘿,这个问题我之前也踩过坑!回调嵌套不仅让代码看起来像“金字塔”,重复的错误处理也很冗余,维护起来特别头疼。咱们一步步把它改得优雅起来:
核心优化思路:抛弃回调,用Promise/Async-Await替代
Mongoose从4.0版本开始就原生支持Promise了,这意味着我们可以摆脱嵌套回调的噩梦,用更扁平的方式写异步代码,还能统一处理错误。
方案1:Promise链式调用(适合不熟悉async/await的场景)
我们可以把每个Mongoose方法的回调去掉,它们会返回Promise,然后用.then()链式串联逻辑,所有错误统一在.catch()里处理:
exports.changePassword = (req, res) => { const { token, password, confirmPassword } = req.body; // 先补上原代码缺失的密码一致性校验 if (password !== confirmPassword) { return res.status(400).send({ msg: 'Passwords do not match!' }); } User.findOne({ resetPasswordToken: token }) .then(user => { if (!user) { return res.status(400).send({ msg: 'Invalid token or token has been used!' }); } // 生成加密密码 const hash_password = bcrypt.hashSync(password, 10); // 合并两次更新操作,减少数据库请求 return User.findOneAndUpdate( { _id: user._id }, { hash_password, resetPasswordToken: '' }, // 一次更新两个字段 { new: true } // 可选:返回更新后的用户文档 ); }) .then(() => { // 所有操作成功,返回结果 res.status(200).json({ status: 1, data: 'Your password has been changed.' }); }) .catch(err => { // 统一捕获所有数据库操作错误 res.status(400).send({ msg: err.message || 'Failed to update password.' }); }); };
方案2:Async/Await(最简洁易读的现代写法)
如果你的Node.js版本在8.0以上(现在大部分项目都满足),用async/await会让代码看起来像同步逻辑,可读性拉满:
exports.changePassword = async (req, res) => { try { const { token, password, confirmPassword } = req.body; // 密码一致性校验 if (password !== confirmPassword) { return res.status(400).send({ msg: 'Passwords do not match!' }); } // 查找对应token的用户 const user = await User.findOne({ resetPasswordToken: token }); if (!user) { return res.status(400).send({ msg: 'Invalid token or token has been used!' }); } // 加密密码并更新用户数据 const hash_password = bcrypt.hashSync(password, 10); await User.findOneAndUpdate( { _id: user._id }, { hash_password, resetPasswordToken: '' } ); // 操作成功返回 res.status(200).json({ status: 1, data: 'Your password has been changed.' }); } catch (err) { // 统一处理所有异步错误 res.status(400).send({ msg: err.message || 'Failed to update password.' }); } };
关于你提到的done参数
done一般是回调式异步函数里用来传递结果或错误的参数(比如Passport.js的认证策略),但对于Mongoose操作来说,Promise/async-await是更现代、更简洁的方案,完全可以替代回调+done的写法,所以不建议用done来优化,反而会让代码回到回调嵌套的老路。
错误处理的关键注意点
- 不需要每个回调都处理错误:用Promise的
.catch()或者async/await的try/catch可以统一捕获所有异步操作的错误,避免重复写if(err)的冗余代码 - 业务逻辑错误单独处理:比如token无效、密码不匹配这类属于业务校验失败的情况,需要单独判断返回,不属于数据库操作错误
- 数据库错误统一捕获:比如连接失败、更新出错等底层错误,都可以在统一的错误捕获块里处理,返回友好的错误信息
内容的提问来源于stack exchange,提问作者Jenny Mok
相关产品推荐
相关产品推荐

