2

when I have a variable number of ajax requests, how can I call them using deferreds?

my guess:

//qty_of_gets = 3;

function getHTML(productID, qty_of_gets){

    var dfd = $.Deferred(),
            i = 0,
            c = 0;

    //this is where there could be some magic to 
    //do multiple ajax posts
    //obviously I'm out of my depth here...
    while (i <= qty_of_gets){

        dfd.pipe(function(){
            $.get("queries/html/" + product_id + i + ".php");
        });                       
    i++
    }
    dfd.done(function(){

        while (c <= qty_of_gets){
           $('myDiv').append(c);
           c++;
        }

    });
}
1252748
  • 14,597
  • 32
  • 109
  • 229
  • 2
    If you just stick the deferred object that is automatically returned by an ajax request into an array, and throw the array into $.when with a little apply(), you'll get there! – adeneo Feb 20 '13 at 20:50

2 Answers2

6

If you want to execute the Ajax calls sequentially, you have to return the promise from the callback and also attach a new callback to the last promise object:

var dfd = $.Deferred(),
   promise = dfd.promise(),
   i = 0,
   c = 0;

while (i <= qty_of_gets) {
    // needs an IIFE
    (function(i)
        promise = promise.then(function(){
            return $.get("queries/html/" + product_id + i + ".php");
        });
    }(i++));                       

}

promise.done(function(){

    while (c <= qty_of_gets){
       $('myDiv').append(c);
       c++;
    }

});

// resolve deferred
dfd.resolve();

As of jQuery 1.8, you should use .then instead of .pipe.

Another problems is (in your example at least) that at the time the callbacks are executed, i won't have the value you expect. You can use an immediately invoked function expression to capture the current value of i. See JavaScript closure inside loops – simple practical example for more info.


There is no clean solution for getting the results. I think the best you could do is adding the results to an array and access that array in the .done callback. I.e.:

var results = [];

while (i <= qty_of_gets) {
    // needs an IIFE
    (function(i)
        promise = promise.then(function(){
            return $.get("queries/html/" + product_id + i + ".php")
                     .then(function(result) {
                       results[i] = result;
                     });
        });
    }(i++));                       

}

promise.done(function(){
    // do something with `results`
});
Community
  • 1
  • 1
Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
  • Thank you. I read in this page _the problem is that the variable i, within each of your anonymous functions, is bound to the same variable outside of the function._ and many other similar explanations. But I don't understand if `i` is bound to the interior of the anonymous function, why isn't it passed in again each time it goes through the loop. Sure, it exists outside, but isn't its value constantly being incremented and passed in. Is it saying that the first time you make that anonymous function, the value is permanently set somehow so that javascript will then always read `console.log(1)` – 1252748 Feb 20 '13 at 21:46
  • Couple more questions: You say "you have to return the promise from the callback". So done is the callback function of promise. So what you're doing is chaining like `promise.then($.get("queries/1.php").then.($.get("queries/2.php")...`? Why do you put `resolve()` after `done()`? The jquery API says "When the Deferred is resolved, any doneCallbacks added by deferred.then or deferred.done are called. " Thanks again for all the help! – 1252748 Feb 20 '13 at 21:55
  • In each iteration of the loop, a new anonymous function is created. You are never calling the same function twice. You could however create a function `function foo(promise, i) { return promise.then(function(){...}); }` and call this one instead of the anonymous function: `promise = foo(promise, i)`. It's the same. The point is to call a function to create a new scope. Regarding the second question, it's the same as calling `promise.then(function() { return $.get(...); }).then(...).then(...);`. `.then` always returns a new promise. – Felix Kling Feb 20 '13 at 21:59
  • If I don't call `.resolve`, none of the callbacks will be ever called. We want the first promise object to call its `.then` callback so that it starts making those Ajax requests. It won't do that until the deferred object is resolved. Note that the `.done` callback is added to the *last* promise object in the chain, not the deferred object itself. I could also call `.resolve` immediately after the deferred object was created, it would not make a difference. – Felix Kling Feb 20 '13 at 22:01
  • I just meant that physically on the page, it's called below the `done()` function. if the `done` can't be called before the promise is resolved, why is it below the `done`? – 1252748 Feb 20 '13 at 22:04
  • `.done(...)` **adds** a done callback. It does not *call* the callback. The callback is only called *after* the deferred object is resolved. `.done` is basically the same as `.then`, it just does not return a new promise object. Compare http://jsfiddle.net/ym4c6/ against http://jsfiddle.net/ym4c6/2/. – Felix Kling Feb 20 '13 at 22:06
  • Oh, of course. Sorry. Infinite thanks for these detailed explanations. – 1252748 Feb 20 '13 at 22:08
  • No problem :) All this deferred object and promise stuff can be very confusing, it least it was for me. It took me a good while to feel comfortable with it. – Felix Kling Feb 20 '13 at 22:09
  • Yeah, no kidding! How can I access the data returned by those promises? my `$('#myDiv').append()` isn't getting any data from the gets.. – 1252748 Feb 20 '13 at 23:16
  • yeah, but I don't even get `console.log('test')` to print when I put it inside the `then()` statement. and and looking at results shows an empty array. – 1252748 Feb 20 '13 at 23:38
  • Are the Ajax calls successful? If not, the callbacks won't be called. – Felix Kling Feb 20 '13 at 23:41
  • yeah, just doing `$.get("order_queries_templates/html/" + name + "_additional_options.php", function(data){ console.log(data); });` prints it out – 1252748 Feb 20 '13 at 23:46
  • Actually `promise = promise.then(function(){ console.log('test');` doesn't print anything either.. – 1252748 Feb 20 '13 at 23:59
  • Are you calling `dfd.resolve();`? I tried to create a jsFiddle example, but it looks like it has some problems with its Ajax service. All I can offer you is http://jsfiddle.net/ym4c6/4/. So, conceptually it works, you must be missing something. – Felix Kling Feb 21 '13 at 00:15
  • I have no idea what I was doing so wrongly. Something out of place I guess. Thanks a million. – 1252748 Feb 21 '13 at 00:40
  • Aha, I see what you did. In your answer, you did `.then(function(result) {results[i] = result;});`, but in the fiddle, it's `.done(function() { results[i] = i;`. What's the difference there? – 1252748 Feb 21 '13 at 19:22
  • Mmh. I'm not sure. Maybe `then` is the equivalent to the `complete` event handler and `done` is the equivalent to `success`. – Felix Kling Feb 21 '13 at 20:16
  • so each `promise.then..get()` can have it's own done, then there is the `done()` at the very end which applies to the promise itself(?) I think I'll post a new question about this to see exactly what's going on. – 1252748 Feb 21 '13 at 20:25
  • Well, the `$.get().done()`'s are used to do something which each response from the Ajax call. It's totally independent from the other promises. You could also pass a callback directly to `$.get` instead of using `.done`. The last `promise.done` is executed when all Ajax calls are finished (successful). – Felix Kling Feb 21 '13 at 20:30
  • you mean like, `$.get('get.html', function(data){results[i]=data});`? – 1252748 Feb 21 '13 at 20:37
3

Close, you need to return a promise object from the .pipe callback.
See felix's answer, the next sample has additional issues than just the return missing.

dfd.pipe(function(){
    return $.get("queries/html/" + product_id + i + ".php");
});  

also, I don't think it's actually written anywhere yet, but .pipe is implemented like this in the core in recent versions:

promise.pipe = promise.then

therefore, you could should replace dfd.pipe with dfd.then
Reference: http://api.jquery.com/deferred.pipe/

An alternative as adeneo mentioned is to use $.when

function getHTML(productID, qty_of_gets) {

    var dfdArr = [];

    while (i <= qty_of_gets) {
        dfdArr.push($.get("queries/html/" + product_id + i + ".php"));
        i++
    }
    $.when.apply(null, dfdArr).done(function () {

        for (response in arguments) {
            $('#myDiv').append(response[0]);
        }

    });
}
Kevin B
  • 94,570
  • 16
  • 163
  • 180
  • FYI, this is a relatively new development. As per [the spec](https://github.com/promises-aplus/promises-spec), `then` should return a promise, but the original promise implementation in jQuery hasn't done that; they instead created a different `pipe` method. To be more compliant with the spec (they're still not fully compliant), they altered the `then` behavior, so they then just aliased `pipe` to `then`. I believe `pipe` has been deprecated since 1.8, and should be avoided. – Joseph Silber Feb 20 '13 at 20:52
  • @JosephSilber right, the depreciation notice just never made it to the API site due to the api site not getting updated at all for 1.8 until after 1.9 was released. It is of course now documented as depreciated. – Kevin B Feb 20 '13 at 20:58
  • Thanks for this. Can you explain a little bit how the `$.when.apply(null, dfdArr).done` is working. And exactly what's done? Also, does pushing into an array like this keep the various `get`s in a sequential sort of chain? `product_id+1` is appended, then `product_id+2`, and so on? Or are they pushed into array in the order of which is most quickly returned? The while loop will keep executing without waiting for the result of the asynchronous request, right? Thanks again – 1252748 Feb 20 '13 at 21:03
  • the `.when` method accepts n arguments, using `.apply` with it applies the array of deferred objects as arguments to the `.when` method making it easier to apply a dynamic number of arguments to it. Neither this method nor your method guarantees what order the requests will complete, however it does guarantee each one will be triggered with the correct `i` value and that the `.done` at the end will be triggered when they are all successful if they are all successful. – Kevin B Feb 20 '13 at 21:07
  • The difference between the two is the .then way will only give you the results of the last one sent, while the .when way will give you access to all of the return data from all of the requests, in order by looking at the arguments returned. For example, you could do `for (args in arguments) { console.log(args[0]) }` to get the returned text from each request in order from `0` to `n`. – Kevin B Feb 20 '13 at 21:09
  • Do you think you could show an example of this in your answer? I'm not really getting the `for(args in arguments)` or how I could use it to append in order? Sorry to be thick.. – 1252748 Feb 20 '13 at 21:15
  • I notice that this appends the index of each request. Is there any way to get the HTML the `get`s fetch? Thanks! – 1252748 Feb 21 '13 at 00:09
  • console.log response to see what it contains – Kevin B Feb 21 '13 at 01:16