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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 10:15:19