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

