简体   繁体   中英

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

I need to collect logs for different devices from a backend, to export it to a csv-file. Problem is the number of devices can vary. So I ask the backend for the amount of devices and loop through the requests for the logs.

The problem is the for... loop runs too faster than I get the responses to $.post , so the loop finishes before I get the responses. After some research I could handle that behaviour, but now I was asked to add data to the request, for which I have no reference to the according device is stored. So I added the device names and spots I need to poll in an external js-file, so I have a defined list to loop through.

I tried to use the index of the for loop to fetch the device names, which didn't work, since the loop was too fast. Now I have created a workaround, by defining a counter and pushing the devices in another variable. This doesn't feel "clean" and there should be a better way to poll the data and keep track for which device it is.

The code so far:

     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);
     }

The part which I am not satisfied with is:

          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";
                      });

To output the device that I collected the spots for I use the counter, to track device names. I have the hunch this is not the best and "cleanest" method, so I'd like to ask if there is any better way to deal with the asynchronity (sp?) in terms of collecting the right device for which the post is made and also to trigger the DL if everything is done.

Since my question doesn't seem to be clear, perhaps I need to narrow it down. The code works, but it seems just to be tinkered by me and there should be cleaner ways to

A) handle the posts/gets, since the outstring for CSV is just put together in the way the requests are answered, so not device 1 is the first in the csv, but the one which comes first. $(document).ajaxStop waits for everything to be finished, but not to be finished in the right order.

B) I need to relate the index of the for loop to the device I poll the data for. I used additional variables, that I count up to go through an additional array. Is there any better way?

The problem is that you need to run in order the methods that are invoked after you get the response to the AJAX calls.

To do so you must undertand that all jQuery AJAX calls return promises. Instead of passing the code to run as a parameter, you can do the following:

var functionToRunAfterResponse(params) = function(params) {
   // do something with params
};

var promise = $.post(/*...*/); // some kind of ajax call

promise.then(functionToRunAfterResponse);

Note that the functionToRunAfterResponse will receive the response data as parameter. Ie it's equivalent to:

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

This is how it works for a simple call. See $.then and deferred and promise docs.

If you have to make a bunch of calls, you have to do the following:

  • store the promises of the AJAX calls, for example in an array
  • check that all the promises are fulfilled (ie that all responses have arrived)
  • run the code in the required order. Ie run the promise.then in the right order to get the desired result.

To do so, you can use $.when .

The code structure should be like this (pseudocode):

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
      });

NOTE: Here you've got a deeper explanation , but, basically, as $.when expects a series of promises, like this: $.when(promise1, promise2, ...) and you have an array promises[] , you have to use apply to make the call, so that the items in the array are passed as individual parameters.

FINAL NOTES:

1) take into account that AJAX calls can fail. In that case, instead of a resolved promise, you get a failed promise. You should check it. If any of the promises fails, $.when will not work as desired:

The method [when] will resolve its master Deferred as soon as all the Deferreds resolve, or reject the master Deferred as soon as one of the Deferreds is rejected.

2) To handle errors, then has two parameters:

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

3) When you make the calls as I've explained, they are all executed in parallell, and the responses will arrive in any order. Ie there is no warranty that you'll receive all the responses in the order you made the calls. They can even fail, as I explained in 1) That's why you must use when and run then in order again

4) Working with asynchronous methods is great, but you have the responsibility to check that they didn't fail, and run in the right order the code that you need to run after you get the responses.

I can't understand your question, but the main problem is the asynchronity, right?

Try to call the functions asyncronously (summary version):

// 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/

This example is in spanish but in this answer you have the code commented in english :).

Sorry for my english :S.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM