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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.04 07:09:01