jQuery, $.ajax with array of urls

2019-02-12 18:02发布

I have a simple array of urls, and I want to load each one with jQuery. I was using $.get, but I cannot seem to get it to work with $.Deferred, so I switched to $.ajax - I almost have it working, but the results I am getting are .. odd. I was hoping someone could help me make this work better.

var results = [], files = [
   'url1', 'url2', 'url3'
];

$.when(
   $.ajax(files[0]).done(function(data) { 
      results.push(data); console.log("step 1.0"); 
   }),
   $.ajax(files[1]).done(function(data) { 
      results.push(data); console.log("step 1.1"); 
   }),
   $.ajax(files[2]).done(function(data) {
      results.push(data); console.log("step 1.2"); 
   })
).then(function(){
   console.log("step 2");
});

This should output..

  • step 1.0
  • step 1.1
  • step 1.2
  • step 2

And then the results array contains the result of all 3 ajax requests. Is this possible?

2条回答
孤傲高冷的网名
2楼-- · 2019-02-12 18:26

First off, you have to decide if you want your three ajax calls to be processed in parallel (running all at the same time, with less overall running time) or in sequence where one ajax calls runs, completes and then you launch the next ajax call. This is a key design decision that impacts how you do this.

When you use $.when() you are launching all three ajax calls in parallel. If you examine the results only when all have completed, you can still process the results in a specific order (since you will be processing them only when all results are available and they will be available in the order requested). But, when doing it this way all the ajax calls will be initially sent at once. This will give you a better end-to-end time so if this is feasible for the types of requests, this is generally a better way to do it.

To do that, you can restructure what you have to something like this:

Run in Parallel

var files = [
   'url1', 'url2', 'url3'
];

$.when($.ajax(files[0]),$.ajax(files[1]),$.ajax(files[2])).done(function(a1, a2, a3) {
   var results = [];
   results.push(a1[0]);
   results.push(a2[0]);
   results.push(a3[0]);
   console.log("got all results")
});

Because you're waiting until the .done() handler for $.when() has been called, all the ajax results are ready at once and they are presented by $.when() in the order they were requested (regardless of which one actually finished first), so you get the results as quick as possible and they are presented in a predictable order.

Note, I also moved the definition of the results array into the $.when() done handler because that's the only place you know the data is actually valid (for timing reasons).


Run in Parallel - Iterate Arbitrary Length Array

If you had a longer array, you might find it better to iterate through your array with something like .map() to process them all in a loop rather than listing them individually:

var files = [
   'url1', 'url2', 'url3', 'url4', 'url5', 'url6', 'url7'
];

$.when.apply($, files.map(function(url) {
    return $.ajax(url);
})).done(function() {
    var results = [];
    // there will be one argument passed to this callback for each ajax call
    // each argument is of this form [data, statusText, jqXHR]
    for (var i = 0; i < arguments.length; i++) {
        results.push(arguments[i][0]);
    }
    // all data is now in the results array in order
});

Sequence the Ajax Calls

If, on the other hand, you actually want to sequence your ajax calls so the 2nd one doesn't start until the first one finishes (something that may be required if the 2nd ajax call needs results from the 1st ajax call in order to know what to request or do), then you need a completely different design pattern and $.when() is not the way to go at all (it only does parallel requests). In that case, you probably just want to chain your results with x.then().then() and you can then output the log statements in the sequence you asked for like this.

  $.ajax(files[0]).then(function(data0) {
      console.log("step 1.0");
      return $.ajax(files[1]);
  }).then(function(data1) {
      console.log("step 1.1");
      return $.ajax(files[2]);
  }).done(function(data2) {
      console.log("step 1.2");
      // all the ajax calls are done here
      console.log("step 2");
  });

Console Output:

step 1.0
step 1.1
step 1.2
step 2

This structure can also be put into a loop to automatically run it for N sequential ajax calls if your array of files is longer. While you could collect the results as you go into the results array, often the reason things are done sequentially is that the prior results are consumed by the next ajax call so you often only need the final result. If you wanted to collect the results as you go, you could certainly push them into the results array at each step.

Notice, the advantages that promises offer here in that you can sequence operations while staying at the same top level of nesting and not getting further and further nested.


Sequence the Ajax Calls - Iterate Arbitrary Length Array

Here's what the sequencing would look like in a loop:

var files = [
   'url1', 'url2', 'url3', 'url4', 'url5', 'url6', 'url7'
];

var results = [];
files.reduce(function(prev, cur, index) {
    return prev.then(function(data) {
        return $.ajax(cur).then(function(data) {
            console.log("step 1." + index);
            results.push(data);
        });
    })
}, $().promise()).done(function() {
    // last ajax call done
    // all results are in the results array
    console.log("step 2.0");
});

Console Output:

step 1.0
step 1.1
step 1.2
step 1.3
step 1.4
step 1.5
step 1.6
step 2

The Array.prototype.reduce() method works handily here because it accumulates a single value as you process each individual array element which is what you need to do as you add .then() for each array element. The .reduce() iteration is started with an empty/resolved promise with $().promise() (there are other ways to also create such a promise) which just gives us something to start doing .then() on that is already resolved.

查看更多
来,给爷笑一个
3楼-- · 2019-02-12 18:34

You should access the return values from .then instead of each .done. Additionally, .map is your friend.

var results = [], files = [
   'url1', 'url2', 'url3'
];

$.when.apply($, $.map(files, function (file) {
    return $.ajax(file);
})).then(function (dataArr) {
    /* 
     * dataArr is an array of arrays, 
     * each array contains the arguments 
     * returned to each success callback
     */
    results = $.map(dataArr, function (data) {
        return data[0]; // the first argument to the success callback is the data
    });
    console.log(results);
});

the arguments passed to .then will be in the same order that they were passed to .when

查看更多
登录 后发表回答