Deferred and promise with loops

1.3k views Asked by At

I am trying to use deferred and promises within the context of a loop and I fail doing so.

I need to call a first method asynchronously. This first method (let´s call it getArray()) returns an array.

For each element of the array, I need to call a second method, let´s call it getData(id). The id is stored in the array returned by getArray.

Finally, once I have the result of getData(), I need to use the result to call a third method, let´s call it getDetails().

Once all the calls are done and the loop is finished, I need to return the array, with extra data and details.

I created a jsfiddle: http://jsfiddle.net/chevdor/953sLkts/

var myarray = [];

var log = function(s){
    $('#console').append('<p>'+s+'</p>');
};

var get1 = function() {
    var dfd = jQuery.Deferred();
    log('Running get1');
    setTimeout(function() { 
        var res = [1, 2, 3, 4];

        // that´s probably crap...
        for (var i= 0; i< res.length; i++){
            get2(res[i])
                .done(get3(res[i])
                    .done(get4(res[i]))
            );
        }

        dfd.resolve(res);
    }, Math.floor(400 + Math.random() * 1000));
    return dfd.promise();
};

var get2 = function(index) {
    var dfd = jQuery.Deferred();
    log('Running get2');

    setTimeout(function() { 
        var res = {
            index: index,
            val: index * 2
        };
        myarray[index]= res;
        dfd.resolve(res);
    }, Math.floor(400 + Math.random() * 1000));
    return dfd.promise();
};

var get3 = function(index) {
    var dfd = jQuery.Deferred();
    log('Running get3');

    setTimeout(function() { 
        var res = {
            index: index,
            val2: index * index
        };
        myarray[index].extra =  res;

        dfd.resolve(res);
    }, Math.floor(400 + Math.random() * 1000));
    return dfd.promise();
};

var get4 = function(index) {
    var dfd = jQuery.Deferred();
    log('Running get4');

    setTimeout(function() { // Resolve after a random interval
        var res = {
            index: index,
            val2: index * index
        };
        dfd.resolve(res);
    }, Math.floor(400 + Math.random() * 1000));
    return dfd.promise();
};

log('start');
get1();

to illustrate. It definitely does not work and look ugly...

Can you explain what would be the proper way to do that?

2

There are 2 answers

2
Evilsanta On

May I introduce you to $.when.

From your code, it looks like your get2(), get3(), get4() are the getData(), getDetail(). Assume they all are made like a promise. Then you can do this:

getArray().then(function(array){
    var allpromises=_.map(array,function(one){
        var toReturn={id:one}
        // let's assume the array is [1,2,3...]; then one= 1,2,3...
        return getData(one).then(function(onedata){ 
            //onedata={name:'foo',id:1} just an example.
            toReturn['data']=onedata;
            return getDetail(onedata);
        }).then(function(detail){ // here you get detail
            toReturn['detail']=detail;
            return jQuery.resolve(toReturn);
        });
    });
    return jQuery.when(allpromises); // This is a promise, resolves when every promise in the array all resolved. the resolved data is the same length array (same length as the allpromises. 
}).then(function(dataarray){
    console.log(dataarray); //each entry should be like the "toReturn". {id:1,data:{name:'foo',id:1},detail:{object}}
})

I was a big fan of bluebird promise, the keyword was .all() instead of .when(). I am not exactly sure how jquery promise work, but I believe it is a very similar structure. Hope this helps. Let me know if it is unclear :)

EDIT: Sorry for the confusion, to use $.when on array of promises, the syntax is:

$.when.apply($, [promise1,promise2,promise3]).then(function(data){
})

Note the "data" here is only the resolved from promise1, I am not familiar with jquery. But I do recommend bluebird promise. Which is faster and cleaner.

11
guest271314 On

Try creating an array p , .push() results of $.when(get2(res[i]), get3(res[i]), get4(res[i])) to p ; call $.when.apply with p as array of jQuery promise objects passed ; check results of dfd.resolve(res) , myarray at .then onFulfilled handler of get1

var myarray = [];

var log = function(s){
    $('#console').append('<p>'+s+'</p>');
};

var get1 = function() {
    var dfd = jQuery.Deferred();
    log('Running get1');
    var p = [];
    setTimeout(function() { 
        var res = [1, 2, 3, 4];

        // that´s probably crap...
  for (var i= 0; i< res.length; i++){
   p.push($.when(get2(res[i]), get3(res[i])
            , get4(res[i])))
            
  };
        
        $.when.apply($, p).then(function() {
          dfd.resolve($.makeArray(arguments))
        })
        
        
    }, Math.floor(400 + Math.random() * 1000));
    return dfd.promise();
};

var get2 = function(index) {
    var dfd = jQuery.Deferred();
    log('Running get2');

    setTimeout(function() { 
        var res = {
            index: index,
            val: index * 2
        };
        myarray[index]= res;
        dfd.resolve(res);
    }, Math.floor(400 + Math.random() * 1000));
    return dfd.promise();
};

var get3 = function(index) {
    var dfd = jQuery.Deferred();
    log('Running get3');

    setTimeout(function() { 
        var res = {
            index: index,
            val2: index * index
        };
        myarray[index] = {"extra":res};
        
        dfd.resolve(res);
    }, Math.floor(400 + Math.random() * 1000));
    return dfd.promise();
};

var get4 = function(index) {
    var dfd = jQuery.Deferred();
    log('Running get4');

    setTimeout(function() { // Resolve after a random interval
        var res = {
            index: index,
            val2: index * index
        };
        dfd.resolve(res);
    }, Math.floor(400 + Math.random() * 1000));
    return dfd.promise();
};

log('start');
get1().then(function() {
  console.log("res:", arguments, "myarray:", myarray);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js">
</script>
<h1>Test</h1>
<span id='console'></span>

jsfiddle http://jsfiddle.net/953sLkts/9/