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

JavaScript循环内异步调用未等待响应导致数据赋值丢失的问题求助

解决JavaScript循环中异步操作未等待的问题

这个问题我太熟了!本质是同步循环不会等待异步操作完成——不管是你用的for...in还是forEach,都会一股脑把循环跑完,等异步回调回来的时候,循环早就结束了,还会因为变量作用域问题(比如你用了var res,所有回调会共享同一个res引用)导致数据错乱。

核心问题分析

  1. var的作用域问题:for (var res in results)里的res是函数级作用域,所有异步回调执行时,res已经指向数组最后一个元素了,根本不是当前迭代的那个。
  2. 循环不等待异步: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);
}

关键修改点说明

  1. async/await替代$.when().done():让代码逻辑变成“同步风格”,每次迭代都会等待callTestOutputData完成后再继续。
  2. for...of替代for...in:for...in是遍历对象键的,results是数组,for...of更适合遍历数组元素,且天然支持await。
  3. 递归调用加await:因为OnSuccess现在是异步函数,必须等待递归的下载操作完成,才会进入后续的导出逻辑。
  4. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 06:12:42