如何处理对循环中发出的多个异步请求(AJAX 调用)的响应

How to handle response to several asynchronous request (AJAX calls) made in a loop

本文关键字:异步 AJAX 响应 调用 请求 处理 何处理 循环      更新时间:2023-09-26

我需要从后端收集不同设备的日志,以将其导出到csv文件。问题是设备的数量可能会有所不同。因此,我向后端询问设备数量并循环访问日志请求。

问题是for...循环的运行速度比我获得对$.post的响应快,所以循环在我得到响应之前完成。经过一些研究,我可以处理这种行为,但现在我被要求向请求添加数据,我没有参考存储相应的设备。因此,我在外部js文件中添加了需要轮询的设备名称和点,因此我有一个定义的列表来循环。

我尝试使用 for 循环的索引来获取设备名称,但没有奏效,因为循环太快了。现在,我已经创建了一个解决方法,通过定义一个计数器并在另一个变量中推送设备。这感觉并不"干净",应该有一种更好的方法来轮询数据并跟踪它是哪个设备。

到目前为止的代码:

     function collectData() {
        var outString = "";
        var lots of stuff I can pre-fetch
        var logs = function (outString, saveCSV) {
           var postString;
           var devices = [];
           var count = 0;
           for (i = 1; i <= maxDevice; i++) {
              postString = build postString in loop
              devices.push(spots[0][i - 1]);
              $.post('/path/foobar.db',
                      postString,
                      function (data) {
                         outString += "Spotlist for: " + spots[0][count] + "'n";
                         count++;
                         outString += data.replace(/{/g, "").replace(/}/g, "").replace(/:/g, ";").replace(/,/g, "'n").replace(/'"/g, "");
                         outString += "'n'n";
                      });
              postString = "/path/eventlog.csv?device=" + i;
              $.get(postString,
                      function (data) {
                         outString += "Event Log: 'n" + data + "'n";
                      });
              postString = "/path/errorlog.csv?device=" + i;
              $.get(postString,
                      function (data) {
                         outString += "Error Log: 'n" + data + "'n";
                      });
           }
           $(document).ajaxStop(function () {
              saveCSV(outString, filename);
              $(this).unbind('ajaxStop');
           });
        };
        var saveCSV = function (outString, filename) {
           var tempString = "data:text/csv;charset=utf-8," + outString;
           var encodedUri = encodeURI(tempString);
           var a = document.getElementById("dlLink");
           if (window.navigator.msSaveOrOpenBlob) {
              blobObject = new Blob([outString], {type: 'text/csv;charset=utf-8'});
              window.navigator.msSaveBlob(blobObject, filename);
           } else
           {
              a.setAttribute("href", encodedUri);
              a.setAttribute("download", filename);
              a.click();
           }
        };
        outString = lots of predefined and pre-fetched stuff
        outString += "Device data: 'n'n";
        logs(outString, saveCSV);
     }

我不满意的部分是:

          for (i = 1; i <= maxDevice; i++) {
              postString = "get = {" + i + ":en:[";
              for (j = 0; j < spots[i].length; j++) {
                 postString += '"' + spots[i][j] + '",';
              }
              postString = postString.slice(0, -1) + "]}";
              devices.push(spots[0][i - 1]);
              $.post('/path/foobar.db',
                      postString,
                      function (data) {
                         outString += "Spotlist for: " + spots[0][count] + "'n";
                         count++;
                         outString += data.replace(/{/g, "").replace(/}/g, "").replace(/:/g, ";").replace(/,/g, "'n").replace(/'"/g, "");
                         outString += "'n'n";
                      });

为了输出我收集斑点的设备,我使用计数器来跟踪设备名称。我有预感这不是最好和"最干净"的方法,所以我想问一下是否有更好的方法来处理异步性(sp?),以收集正确的设备,如果一切都完成,也会触发DL。

由于我的问题似乎不清楚,也许我需要缩小范围。代码有效,但它似乎只是被我修补了,应该有更干净的方法来

A) 处理帖子/获取,因为 CSV 的输出字符串只是以应答请求的方式组合在一起,因此不是设备 1 是 csv 中的第一个,而是第一个。 $(document).ajaxStop等待一切完成,但不是以正确的顺序完成。

B) 我需要将 for 循环的索引与我轮询数据的设备相关联。我使用了额外的变量,我计算这些变量以通过一个额外的数组。有没有更好的方法?

问题是您需要按顺序运行在获得对 AJAX 调用的响应后调用的方法。

要做到这一点,你必须明白所有jQuery AJAX调用都返回promise。您可以执行以下操作,而不是将代码作为参数传递:

var functionToRunAfterResponse(params) = function(params) {
   // do something with params
};
var promise = $.post(/*...*/); // some kind of ajax call
promise.then(functionToRunAfterResponse);

请注意,functionToRunAfterResponse将接收响应数据作为参数。 即它等效于:

promise.then(function(reponseData) {
  functionToRunAfterResponse(responseData);
});

这就是它用于简单呼叫的工作方式。请参阅 $.then 和延期和承诺文档。

如果您必须拨打一堆电话,则必须执行以下操作:

  • 存储 AJAX 调用的承诺,例如在数组中
  • 检查所有承诺是否兑现(即所有回复是否已到达)
  • 按所需顺序运行代码。 即以正确的顺序运行promise.then以获得所需的结果。

为此,您可以使用 $.when。

代码

结构应该是这样的(伪代码):

var promises = [];
// Make the calls, and store the promises
for(/**/) {
  promises.push( $.post or some other kind of ajax call );
}
// Ensure that all the responses have arrived
$.when.apply($, promises)  // See below note
.then(function() {
   for each promise in promises
      promise[i].then(function(reponseData) {
         // run the necessary code
      });

注意:在这里,您有更深入的解释,但是,基本上,正如$.when所期望的那样,一系列承诺,如下所示:$.when(promise1, promise2, ...)并且您有一个数组promises[],您必须使用 apply 进行调用,以便数组中的项目作为单独的参数传递。

结语:

1) 考虑到 AJAX 调用可能会失败。在这种情况下,你得到的不是已解决的承诺,而是失败的承诺。你应该检查一下。如果任何承诺失败,$.when将无法按预期工作:

方法 [when] 将在所有延迟解决后立即解析其主节点"延迟",或者在其中一个"延迟"被拒绝后立即拒绝主节点"延迟"。

2) 为了处理错误,then有两个参数:

$.when.apply().then(function() { /* success */ }, function() { /* error */ });

3)当您按照我的解释进行调用时,它们都是并行执行的,并且响应将以任何顺序到达。也就是说,不能保证您将按照拨打电话的顺序收到所有响应。它们甚至可能失败,正如我在 1) 中解释的那样,这就是为什么您必须使用 when 并再次按顺序运行

4)使用异步方法很棒,但您有责任检查它们是否失败,并在获得响应后以正确的顺序运行您需要运行的代码。

我无法理解您的问题,但主要问题是异步性,对吧?

尝试异步调用函数(摘要版本):

// Function that manage the data from ajax
var myResponseFunction = function (data) {
  $('#response').html('data: ' + JSON.stringify(data));
};
// Ajax function, like your post with one parameter (add all you need)
function myAJAXFunction(callback) {
    $.ajax({
        type: 'POST',
        url: '/echo/json/',
        dataType: 'json',
        data: {
            json: JSON.stringify({
                'foo': 'bar'
            })
        }
    }).done(function(response) {
      // Done!! Now is time to manage the answer
      callback(response);
    }).fail(function (jqXHR, textStatus, errorThrown) {
      window.console.error('Error ' + textStatus + ': ' + errorThrown);
    });
}
// Usually, this function it's inside "document.ready()".
// To avoid the ajax problem we call the function and "data manage function" as parameter.
for (i = 1; i <= maxDevice; i++) {
    myAJAXFunction(myResponseFunction);
}

https://jsfiddle.net/erknrio/my1jLfLr/

这个例子是西班牙语的,但在这个答案中,你用英语:)注释了代码。

对不起我的英语:S。