Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Waiting for multiple deferred objects to be finished and use the resolved values

Tags:

jquery

I am trying to figure out a way to wait for multiple deferred objects and process them once done, may be like to start next set for deferred objects.

I am stuck because the result of the following is not the it is expected to be. I am expecting the result as

allDone resovled values are 1,2,3

The actual result is

allDone resovled values are 1,2
var dfd1 = new $.Deferred();
var dfd2 = new $.Deferred();
var dfd3 = new $.Deferred(); 
var dfds = [ dfd1, dfd2, dfd3 ]; 
var resolvedValues = [];

$.when.apply($, dfds).done(function() {     
    dfds.forEach(function(dfd){
        console.log("inloop");      
        dfd.promise().done(function(value) {
            resolvedValues.push(value);         
        });     
    });
    console.log("allDone resovled values are" + resolvedValues);
})

dfd1.resolve(1);
dfd2.resolve(2);
dfd3.resolve(3);
like image 636
user3397062 Avatar asked Jan 21 '16 08:01

user3397062


Video Answer


1 Answers

For the why, see below. But you're overcomplicating it. :-) The callback you give the final promise you get from when gives you the resolved values as arguments:

$.when.apply($, dfds).done(function(a, b, c) {
    // Here, a is 1, b is 2, c is 3
    // Or you can access them on `arguments`
})

Live Example:

var dfd1 = new $.Deferred();
var dfd2 = new $.Deferred();
var dfd3 = new $.Deferred(); 
var dfds = [ dfd1, dfd2, dfd3 ]; 
var resolvedValues = [];

$.when.apply($, dfds).done(function() {
    // Use a trick to turn `arguments` into a real array
    var a = Array.prototype.slice.call(arguments);
    // Show what we got
    console.log("allDone: " + a.join(", "));
})

dfd1.resolve(1);
dfd2.resolve(2);
dfd3.resolve(3);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

Side note: I assume you're dealing with an array on purpose. If you have a fixed number of promises you need to wait on, just use the simpler form

$.when(dfd1, dfd2, dfd3).then(function(a, b, c) {
    // ...
});

Here's why you're getting the very odd result you're getting: jQuery's Deferred/Promise objects have a problem in that they are chaotic: When you call done on them, you don't know whether your callback will be executed synchronously or asynchronously. This is a serious flaw, and one that a true Promises/A+ implementation does not have (the callback is always asynchronous).

jQuery will call the callback asynchronously if the promise is not yet resolved. But it will call it synchronously if it isn't resolved:

var d1 = $.Deferred();
d1.done(function() {
    console.log("I'm called asynchronously");
});
d1.resolve();

var d2 = $.Deferred();
d2.resolve();
d2.done(function() {
    console.log("I'm called synchronously");
});

So what's happening in your code is that the overall done callback that when fires is fired during the done callback on the last promise that got resolved (dfd3). Since the promise isn't marked resolved until after the done callbacks have been completed, when your code runs, dfd1 and dfd2 are resolved but dfd3 is still in the process of being resolved. So your inner callbacks are called synchronously for dfd1 and dfd2 but asynchronously for dfd3. So you're outputting your result before the dfd3 return value has been pushed onto your array.

like image 118
T.J. Crowder Avatar answered Jan 01 '23 10:01

T.J. Crowder