JavaScript循环内异步调用未等待响应导致数据赋值丢失的问题求助
解决JavaScript循环中异步操作未等待的问题
这个问题我太熟了!本质是同步循环不会等待异步操作完成——不管是你用的for...in还是forEach,都会一股脑把循环跑完,等异步回调回来的时候,循环早就结束了,还会因为变量作用域问题(比如你用了var res,所有回调会共享同一个res引用)导致数据错乱。
核心问题分析
var的作用域问题:for (var res in results)里的res是函数级作用域,所有异步回调执行时,res已经指向数组最后一个元素了,根本不是当前迭代的那个。- 循环不等待异步:
forEach和for...in都是同步执行的,里面的$.when().done()异步操作不会阻塞循环,导致所有异步请求同时发起,但回调回来时,obj早就被下一次迭代覆盖,或者循环已经执行到后续的递归/导出逻辑了。
解决方案:用async/await + for...of实现串行等待
这是最直观、易维护的方案,能保证每次迭代都等待异步操作完成后再进入下一次:
self.downloadDailyExcelProcurement = function (filters, excelTmpArr) { self.disableExcelDownloadProcurement(true); $('.useCaseExcelButtonProcurement .oj-button-button .oj-button-label')[0].style.backgroundColor = "gray"; $('.useCaseExcelButtonProcurement .oj-button-button .oj-button-label .demo-download-icon-24')[0].style.color = "#D8D8D8"; var payload = {}; if (typeof filters === "string") { var fill = filters; } else { var fill = self.sendFilters(); if(self.app() === "fusion"){ fill += '&module=Procurement'; }else if (self.app() === "o2r"){ fill += '&module=O2r'; } } if(fill.includes("%3A")){ fill = fill.replace(/%3A/g, ':'); } payload.Endpoint = 'executions/testcollection/' + fill; payload.BeforeSend = function (xhr) { xhr.setRequestHeader('Authorization', 'Basic ' + btoa('guest:oracle123')); $(" .custom-loader-circle").show(); }; // 把OnSuccess改成async函数,支持await payload.OnSuccess = async function (response) { var results = response.results; // 用for...of遍历数组,支持await等待异步操作 for (const res of results) { var obj = {} obj.ferp = res.name; obj.po = "NA" obj.receipt_no = "NA" var test_output_url = res.reference_test_cases[0].automation_tests[0].test_outputs[0] try { // 等待异步请求完成 const testOutputResponse = await self.callTestOutputData(test_output_url); obj.requistion_number = testOutputResponse ? testOutputResponse.value : "NA"; } catch (err) { // 处理请求失败的情况 obj.requistion_number = "NA"; } self.excelTmpArr().push(obj); } // 递归调用时也要加await,避免提前执行后续逻辑 if (response.next) { filters = ((response.next).split('testcollection'))[1]; if (filters[0] === "/") { var test = filters.slice(1, filters.length); } await self.downloadDailyExcelProcurement(test, self.excelTmpArr()); } else { if (results.length === 0) { $(" .custom-loader-circle").hide(); self.disableExcelDownloadProcurement(false); $('.useCaseExcelButtonProcurement .oj-button-button .oj-button-label')[0].style.backgroundColor = "#4d0000"; $('.useCaseExcelButtonProcurement .oj-button-button .oj-button-label .demo-download-icon-24')[0].style.color = "white"; showMessage(self.messages, "No Data to Download", '', 'info'); } else { self.formatForExcel(self.excelTmpArr(), fill, "Procurement"); } } }; payload.OnError = function (data) { showMessage(self.messages, data.status, data.statusText, 'error'); $(" .custom-loader-circle").hide(); }; getData(payload); }
关键修改点说明
async/await替代$.when().done():让代码逻辑变成“同步风格”,每次迭代都会等待callTestOutputData完成后再继续。for...of替代for...in:for...in是遍历对象键的,results是数组,for...of更适合遍历数组元素,且天然支持await。- 递归调用加
await:因为OnSuccess现在是异步函数,必须等待递归的下载操作完成,才会进入后续的导出逻辑。 try/catch捕获异步错误:避免单个请求失败导致整个流程中断。
备选方案:用Promise.all并行处理
如果你想让所有异步请求并行发起(提高性能),可以用Promise.all批量处理:
payload.OnSuccess = function (response) { var results = response.results; // 把每个元素的处理转换成Promise const promiseList = results.map(res => { var obj = {} obj.ferp = res.name; obj.po = "NA" obj.receipt_no = "NA" var test_output_url = res.reference_test_cases[0].automation_tests[0].test_outputs[0] return self.callTestOutputData(test_output_url) .then(testOutputResponse => { obj.requistion_number = testOutputResponse ? testOutputResponse.value : "NA"; return obj; }) .catch(() => { obj.requistion_number = "NA"; return obj; }); }); // 等待所有Promise完成 Promise.all(promiseList) .then(allObjs => { // 一次性把所有结果加入数组 allObjs.forEach(obj => self.excelTmpArr().push(obj)); // 后续逻辑和之前一致 if (response.next) { filters = ((response.next).split('testcollection'))[1]; if (filters[0] === "/") { var test = filters.slice(1, filters.length); } self.downloadDailyExcelProcurement(test, self.excelTmpArr()); } else { if (results.length === 0) { // ...省略原有逻辑 } else { self.formatForExcel(self.excelTmpArr(), fill, "Procurement"); } } }) .catch(err => { showMessage(self.messages, "数据处理失败", err.message, 'error'); $(" .custom-loader-circle").hide(); }); };
这种方式的好处是所有请求同时发起,速度更快,但如果后端接口有并发限制,可能需要回到串行的for...of方案。
内容的提问来源于stack exchange,提问作者etotientz
相关产品推荐
相关产品推荐

