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

指数退避(Exponential Backoff)在Promise中无法返回数据问题排查

问题排查与修复:429重试时Promise无法返回数据

核心问题原因

当触发429状态码需要重试时,你在res.on('end')回调里直接递归调用了request(options, retries + 1),但这个递归返回的Promise没有被当前Promise的resolve方法处理。这会导致最初创建的Promise一直处于pending状态,永远不会向调用者返回结果。

修复方案

修改res.on('end')中429分支的逻辑,等待递归的request执行完成后,将结果传递给当前Promise的resolve:

async function request(options, retries) {
  if (retries > 0) {
    const timeToWait = 15000 * retries;
    console.log(`waiting for ${timeToWait}ms...`);
    await waitFor(timeToWait);
  }

  return new Promise(async (resolve, reject) => {
    const req = https.request(options, (res) => {
      let body = [];

      res.on('data', (chunk) => {
        body.push(chunk);
      });

      res.on('end', async () => {
        try {
          body = Buffer.concat(body);
          body = body.toString();

          if (body.startsWith('<!DOCTYPE html>')) {
            _WARN_('Invalid request', 'There was a problem with your request. The parameter(s) you gave are missing or incorrect.');
          } else if (body.startsWith('Throttled')) {
            _WARN_('Throttled request', 'There was a problem with request limit.');
          }

          body = JSON.parse(body);
        } catch (error) {
          reject(error);
          return;
        };

        const returnObject = ReturnObject(
          !(res.statusCode < 200 || res.statusCode >= 300),
          res.statusMessage,
          res.statusCode,
          body
        )

        if (returnObject.code != 429) {
          resolve(returnObject);
        } else {
          if (retries < maxRetries) {
            console.log('retrying...');
            // 等待递归请求完成,再resolve结果
            const retryResult = await request(options, retries + 1);
            resolve(retryResult);
          } else {
            console.log("Max retries reached. Bubbling the error up");
            resolve(returnObject);
          }
        }
      });
    });

    req.on('error', (error) => reject(error));

    req.on('timeout', () => {
      req.abort();
      reject(new Error(`Lizard API request timed out. Current timeout is: ${Lizard.TIMEOUT} milliseconds`));
    });

    req.end();
  });
}

关键修改点

  • 将外层Promise的回调改为async函数,同时把res.on('end')的回调也改为async,允许在内部使用await等待递归请求的结果。
  • 当需要重试时,用await获取递归request的返回值,再调用resolve(retryResult)将结果传递给上层Promise。
  • 在catch块中添加return,避免出错后后续逻辑继续执行导致异常。

额外优化建议

  • 指数退避通常建议使用指数增长的等待时间(比如1000 * Math.pow(2, retries)),而非线性增长的15000 * retries,可以减少不必要的等待时长。
  • 可以从429响应的Retry-After头中获取服务器建议的等待时间,替代固定值,更贴合API的限流规则。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 06:10:59