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

递归XHR Promise触发404无调用栈,重试逻辑异常原因排查

问题:POST请求重试函数达到最大次数后未正常拒绝,抛出未捕获异常

我写了一个发起POST请求并返回Promise的postToServer函数,代码运行正常:

function postToServer(url, body) { 
  let xhr = new XMLHttpRequest(); 
  xhr.open(`POST`, url, true); 
  xhr.setRequestHeader(`Content-type`, `application/json`); 
  return new Promise((resolve, reject) => { 
    xhr.onload = () => { 
      if(xhr.readyState == XMLHttpRequest.DONE && xhr.status == 200) { 
        resolve(); 
      } else { 
        reject(xhr.status); 
      } 
    }; 
    xhr.send(body); 
  }); 
}

但在实现失败重试逻辑的postToServerAndRetryOnFail函数时遇到了问题:

function postToServerAndRetryOnFail(url, body, failedAttempts = 0) { 
  return new Promise((resolve, reject) => { 
    postToServer(url, body).then(() => { 
      resolve(); 
    }, statusCode => { 
      failedAttempts++; 
      if(failedAttempts <= maxAttempts) { 
        wait(intervalBetweenAttempts).then(() => { 
          postToServerAndRetryOnFail(url, body, failedAttempts); 
        }); 
      } else { 
        reject(statusCode); 
      } 
    }); 
  }); 
}

预期逻辑是请求失败时自动重试,直到达到最大次数后返回拒绝状态,但实际情况是:重试至最大次数后并未正常拒绝,而是抛出uncaught exception: 404错误且无调用栈。请问这是什么原因?


解答

这个问题的核心是你在递归调用时没有把内层Promise的结果和外层的Promise关联起来,导致外层Promise一直处于pending状态,而递归里的reject变成了未被捕获的异常。具体来说:

  1. 你在外层手动新建了一个Promise,但当你递归调用postToServerAndRetryOnFail时,只是调用了函数,却没有把这个递归返回的Promise的resolve/reject和外层Promise的resolve/reject绑定。这就导致当递归到最后一次失败时,那个reject没有被任何Promise链捕获,直接变成了未处理的异常。
  2. 另外,你的代码里的错误处理分支没有正确传递Promise的状态,外层Promise根本不知道递归里发生了什么。

修复后的代码

我们可以去掉外层手动创建的Promise,直接返回postToServer的Promise链式调用,这样递归的结果会自动融入Promise链:

// 假设maxAttempts和intervalBetweenAttempts是全局或传入的变量
function postToServerAndRetryOnFail(url, body, failedAttempts = 0) { 
  return postToServer(url, body)
    .catch(statusCode => {
      failedAttempts++;
      if (failedAttempts <= maxAttempts) {
        // 等待后返回递归调用的Promise,让链延续
        return wait(intervalBetweenAttempts)
          .then(() => postToServerAndRetryOnFail(url, body, failedAttempts));
      } else {
        // 达到最大次数,直接拒绝,这个拒绝会被上层Promise链捕获
        return Promise.reject(statusCode);
      }
    });
}

关键改动说明

  • 去掉了外层的new Promise:Promise链式调用本身就可以返回新的Promise,不需要手动包裹,这能避免"Promise嵌套陷阱"。
  • 重试时返回递归调用的postToServerAndRetryOnFail:这样递归的Promise状态会被传递到当前的Promise链中,上层的调用者能正确捕获到最终的resolve或reject。
  • 用catch替代了then的第二个参数:这是更清晰的错误处理方式,也能避免遗漏一些潜在的错误。

这样修改后,当达到最大重试次数时,Promise.reject(statusCode)会被正确传递到最上层的调用者,不会再出现未捕获的异常。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 09:06:28