Node.js递归异步延迟Promise并行执行时请求抢占问题排查
问题分析与解决方案
你遇到的问题核心是全局变量污染!看你的代码里的callback变量——你没有用var/let/const声明它,所以它会被挂载到全局作用域(Node.js里就是global对象)。当你同时启动两个并行的递归链时,第二个调用的callback会直接覆盖第一个的,导致第一个请求的响应回来后,执行的是第二个链的回调逻辑,自然永远无法触发第一个Promise的resolve。
修复步骤
1. 给callback添加局部声明
把callback = function (...) { ... }改成const callback = function (...) { ... }(或者用箭头函数更简洁),这样每个childRecursive调用里的callback都是独立的局部变量,不会互相覆盖。
2. 其他小优化(可选)
- 用
let/const替代var,避免变量提升导致的潜在问题 - 简化耗时计算的代码逻辑
- 修正递归调用时多余的
req参数(原代码里递归传了req但函数参数没接收)
修复后的完整代码
const req = require('request'); const ASK_EVERY_MS = 500; const AT_MOST_MS = 5000; const childRecursive = (t, resolve, reject, t0, id, masterCounter) => { // 简化耗时计算 const [seconds, nanoseconds] = process.hrtime(t0); const duration = (seconds * 1e9 + nanoseconds) / 1e6; if (duration + t > AT_MOST_MS) { reject('timeout'); return; } const options = { url: 'http://www.google.com', json: false, headers: { timeout: 1000 } }; // 声明局部callback,彻底避免全局污染 const callback = (error, response, body) => { if (!error) { console.log('body', body.substring(0, 100)); if (masterCounter % 5 === 0) { resolve("success!!!"); } else { childRecursive(ASK_EVERY_MS, resolve, reject, t0, id, masterCounter + 1); } } else { console.log('problem with submission GET request: ' + error); reject(error); } }; console.log(`${id} masterCounter is ${masterCounter}`); setTimeout(() => { console.log(`${id} requesting...`); req.get(options, callback); }, t); }; // 给每个Promise单独创建计时起点,避免互相干扰 const first = new Promise((resolve, reject) => { const t0 = process.hrtime(); childRecursive(0, resolve, reject, t0, 'FIRST', 101); }); first.then(() => { console.log('FIRST done'); }).catch(err => console.log('FIRST error:', err)); const scnd = new Promise((resolve, reject) => { const t0 = process.hrtime(); childRecursive(0, resolve, reject, t0, 'SECOND', 201); }); scnd.then(() => { console.log('SECOND done'); }).catch(err => console.log('SECOND error:', err));
为什么之前的代码会出问题?
在JavaScript中,如果一个变量没有被声明(不用var/let/const),它会自动成为全局变量。当你第一次调用childRecursive时,callback被赋值为第一个链的回调;第二次调用时,这个全局的callback被替换成第二个链的回调。所以当第一个请求的响应回来时,执行的是第二个链的回调,只会处理第二个Promise的状态,第一个Promise的resolve永远不会被触发。
额外建议
request库已经被官方标记为废弃状态了,建议换成更现代的HTTP客户端,比如axios或者node-fetch,它们原生支持Promise,不需要手动封装回调,代码会更简洁易维护。
内容的提问来源于stack exchange,提问作者igorludi
相关产品推荐
相关产品推荐

