Ajax Promise被覆盖问题:请求失败重试时的Promise处理疑问
我会先调用getLiveData从服务器获取数据,之后渲染UI的部分组件,这些组件会传入Id调用bindLiveData。有时数据返回晚于UI组件渲染,因此需要等待请求完成。但当请求出现异常时问题变得复杂,因为此时会重新调用getLiveData(实际中最多会重试几次)。UI组件在渲染后只会调用一次bindLiveData,所以我计划在失败回调中收集所有Id,待下一次Ajax请求成功时为这些Id分配数据。但此时infoDataPromise会在请求失败时被覆盖,我有以下疑问:
- 之前请求的所有fail回调是否都会触发?
- 如何避免Promise被覆盖?
代码示例:
var infoDataPromise; var mydata; function getLiveData() { infoDataPromise = $.ajax({ type: "POST", dataType: 'JSON', contentType: 'application/json', url: "someUrl", success: function (data) { mydata = data; }, error: function () { getLiveData(); } }); } function bindLiveData(Id) { infoDataPromise.done(() => { if (mydata) { var item = mydata.find(x => x.Id === Id); adjustUIForId(item); } }).fail(() => { mydata = null; }); }
1. 之前请求的所有fail回调是否都会触发?
会触发。每一次$.ajax调用都会生成一个独立的Deferred对象(jQuery对Promise的实现),当某次请求失败时,该请求对应的Deferred对象会进入失败状态,绑定在它上面的fail回调都会被执行。哪怕后续infoDataPromise被新的请求对象覆盖,之前的Deferred对象依然存在,它的状态变化依然会触发对应的回调。
但你当前代码里的fail回调会把mydata设为null,如果多次请求失败,这个操作会被重复执行,可能干扰后续成功请求的处理逻辑。
2. 如何避免Promise被覆盖?
核心思路是不要直接覆盖全局的Promise变量,而是通过维护链式Promise序列或待处理ID列表来管理状态,同时要限制重试次数,避免无限递归。下面提供两种可行的改进方案:
方案一:链式Promise自动延续重试逻辑
把getLiveData改成返回一个Promise,失败时自动重试(带次数限制),这样所有bindLiveData绑定的都是同一个最终会完成的Promise链,无需担心覆盖问题:
var infoDataPromise; var mydata; const MAX_RETRIES = 3; // 设定最大重试次数 function getLiveData(retryCount = 0) { return $.ajax({ type: "POST", dataType: 'JSON', contentType: 'application/json', url: "someUrl" }).then(data => { mydata = data; return data; }).fail(() => { if (retryCount < MAX_RETRIES) { // 重试,返回新的Promise形成链式调用 return getLiveData(retryCount + 1); } // 超过重试次数,抛出错误终止Promise链 throw new Error("达到最大重试次数,加载失败"); }); } // 初始化时获取Promise,后续不再覆盖 infoDataPromise = getLiveData(); function bindLiveData(Id) { infoDataPromise.done(data => { const item = data.find(x => x.Id === Id); adjustUIForId(item); }).fail(() => { // 处理最终失败的情况,比如给UI显示错误状态 console.error("数据加载失败"); }); }
方案二:收集待绑定ID,请求成功后批量处理
如果需要更灵活地控制失败后的ID收集,可以维护一个待处理ID列表,每次请求失败时记录ID,成功时统一处理:
var infoDataPromise; var mydata; const MAX_RETRIES = 3; let pendingIds = []; // 存储请求失败时待绑定的ID let currentRetryCount = 0; function getLiveData() { return $.ajax({ type: "POST", dataType: 'JSON', contentType: 'application/json', url: "someUrl" }).done(data => { mydata = data; // 批量处理所有待绑定的ID pendingIds.forEach(id => { const item = data.find(x => x.Id === id); adjustUIForId(item); }); pendingIds = []; // 清空列表 }).fail(() => { currentRetryCount++; if (currentRetryCount < MAX_RETRIES) { setTimeout(() => getLiveData(), 1000); // 加延迟避免频繁请求服务器 } else { // 最终失败,给所有待处理ID返回错误状态 pendingIds.forEach(id => { adjustUIForId(null); }); pendingIds = []; console.error("达到最大重试次数,数据加载失败"); } }); } // 初始化请求 infoDataPromise = getLiveData(); function bindLiveData(Id) { if (mydata) { // 数据已存在,直接处理 const item = mydata.find(x => x.Id === Id); adjustUIForId(item); } else { // 数据未加载,加入待处理列表 if (!pendingIds.includes(Id)) { pendingIds.push(Id); } // 同时绑定当前Promise的done事件,防止加入列表后请求刚好成功 infoDataPromise.done(data => { const item = data.find(x => x.Id === Id); adjustUIForId(item); }); } }
关键改进点
- 增加重试次数限制,避免无限递归调用
getLiveData - 不再直接覆盖全局Promise,通过链式调用或ID列表维护状态
- 失败时收集待处理ID,成功后统一处理,保证每个UI组件都能获取到数据(或失败状态)
内容的提问来源于stack exchange,提问作者kosnkov

