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

Express路由中Promise二次调用失效,邮箱确认后无响应求助

问题分析与解决方案

核心问题

你代码里最大的坑是在/send的请求处理函数里嵌套注册/response路由。每次调用/send,都会给/response多绑一个回调函数。第一次调用时这个逻辑能跑通,但第二次及之后,用户点验证链接触发的是最早注册的那个/response回调——它对应的是第一次请求的Promise,新请求的Promise根本没机会被resolve,所以会一直卡到5分钟超时,res.json({response:true})当然不会执行。

另外transporter.sendMail用了回调却没处理异步,虽然不是主因,但也得一起修正。

修正方案

直接取消路由嵌套,把/response单独拎出来,用内存(生产环境换数据库)存验证请求和对应的Promise状态,验证成功后触发原请求的resolve。

正确代码示例

// 用内存存待验证请求(生产环境务必换Redis/数据库)
const pendingVerifications = new Map();

// 发送验证邮件的路由
router.post('/send', async (req, res) => {
    const email = req.body.emailPretension;
    // JWT里带上邮箱,方便后续关联请求
    const token = jwt.sign({ 
        email: email,
        data: 'TokenData'
    }, 'MySecretKey', { expiresIn: '5m' });

    const mailConfigurations = {
        from: 'no-reply-please@mygame.fr',
        to: email,
        subject: 'Vérification',
        html: `<body>点击链接验证:<a href="http://你的域名/response?token=${token}">验证邮箱</a></body>`,
    };

    try {
        // 改用await处理邮件发送的异步
        await transporter.sendMail(mailConfigurations);
        console.log('邮件发送成功');

        // 生成Promise并把resolve/reject存起来,等待验证结果
        const verificationPromise = new Promise((resolve, reject) => {
            pendingVerifications.set(email, { resolve, reject });
            // 5分钟超时自动清理并拒绝
            setTimeout(() => {
                if (pendingVerifications.has(email)) {
                    pendingVerifications.delete(email);
                    reject('验证超时');
                }
            }, 300000);
        });

        // 等验证完成再返回结果
        await verificationPromise;
        res.json({ response: true });
    } catch (err) {
        console.log('错误:', err);
        res.status(500).json({ response: false, error: err.message });
    }
});

// 单独的邮箱验证回调路由
router.get('/response', async (req, res) => {
    const tokenReceived = req.query.token;

    try {
        const decoded = jwt.verify(tokenReceived, 'MySecretKey');
        const email = decoded.email;

        // 找到对应的待验证请求,触发resolve
        if (pendingVerifications.has(email)) {
            const { resolve } = pendingVerifications.get(email);
            pendingVerifications.delete(email);
            resolve('验证成功');
        }

        res.send("Email verification success");
    } catch (err) {
        res.send("Email verification failed, possibly the link is invalid or expired");
        // 如果是超时且还有未处理的请求,触发reject
        if (err.name === 'TokenExpiredError' && pendingVerifications.has(decoded?.email)) {
            const { reject } = pendingVerifications.get(decoded.email);
            pendingVerifications.delete(decoded.email);
            reject(err.message);
        }
    }
});

关键改动说明

  • 拆分流路由:/response路由单独定义,再也不会重复注册回调。
  • 用Map关联请求:通过邮箱把原请求的Promise和验证回调绑定,验证成功时直接触发原请求的resolve,让await verificationPromise继续执行,返回预期的res.json。
  • 修复异步处理:transporter.sendMail改用await,避免回调里的错误无法被try/catch捕获。
  • 清理超时请求:超时后自动删除Map里的记录,防止内存泄漏。

额外建议

  • 生产环境别用内存Map,换成Redis或者MongoDB,不然服务器重启后待验证请求全丢。
  • JWT的payload一定要加用户唯一标识(比如邮箱),不然没法关联到对应的请求。
  • 验证链接里填对自己的域名,确保用户能正常访问到/response路由。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 22:52:36