Node.js+Express重置密码出现Callback was already called错误如何解决?
错误原因排查
1. Error: Callback was already called 报错根因
这个错误是async.waterfall流程抛出的,含义是同一个步骤的回调函数被重复调用了,你代码里有两处明显的逻辑漏洞会触发这个问题:
- 当
User.findOne查询不到对应用户(token无效/过期)时,你直接return res.redirect('back')终止了响应,但没有调用done()结束async的流程,后续的异步逻辑还会在后台执行,等到回调触发时就会和已经结束的响应冲突,导致回调重复调用。 - 你没有捕获
user.save、req.logIn操作的错误,如果这两个操作执行报错,错误没有被传递到done回调,也会引发流程异常导致回调重复触发。
2. 收不到确认邮件的根因
你现在用的Gmail SMTP登录逻辑已经失效了:Gmail早在2022年就停止了支持普通账号密码直接登录第三方SMTP服务,你需要先给你的Gmail账号开启两步验证,然后生成应用专用密码,替换你代码中pass字段的普通登录密码才能正常发信。
修正后的代码
app.post('/reset/:token', function(req, res) { async.waterfall([ function(done) { User.findOne({ resetPasswordToken: req.params.token, resetPasswordExpires: { $gt: Date.now() } }, function(err, user) { // 先处理查询错误 if (err) { return done(err); } if (!user) { req.flash('error', 'Password reset token is invalid or has expired.'); // 跳转前调用done终止async流程 done(null, false); return res.redirect('back'); } user.password = req.body.password; user.resetPasswordToken = undefined; user.resetPasswordExpires = undefined; user.save(function(err) { if (err) { return done(err); } req.logIn(user, function(err) { done(err, user); }); }); }); }, function(user, done) { // 如果上一步返回false说明token无效,直接跳过发信 if (!user) { return done(); } var smtpTransport = nodemailer.createTransport('SMTP', { service: 'Gmail', auth: { user: '你的Gmail邮箱地址', pass: '你的Gmail应用专用密码' } }); var mailOptions = { to: user.email, from: '你的发信邮箱地址', subject: 'Your password has been changed', text: 'Hello,\n\n' + 'This is a confirmation that the password for your account ' + user.email + ' has just been changed.\n' }; smtpTransport.sendMail(mailOptions, function(err) { if (err) { req.flash('error', '确认邮件发送失败,请稍后重试'); } else { req.flash('success', '密码重置成功,确认邮件已发送到你的邮箱'); } done(err); }); } ], function(err) { if (err) { req.flash('error', '操作失败,请稍后重试'); return res.redirect('back'); } res.redirect('/'); }); });
内容的提问来源于stack exchange,提问作者Mrinal Anand
相关产品推荐
相关产品推荐

