1

With reference to https://stackoverflow.com/a/13951699/929894, I tried using deferred object in nested ajax loop. However the output is not returning as expected. I have updated my code in fiddle for reference. - https://jsfiddle.net/fewtalks/nvbp26sx/1/.

CODE:

  function main() {
    var def = $.Deferred();
    var requests = [];
    for (var i = 0; i < 2; i++) {
        requests.push(test(i));
    }
    $.when.apply($, requests).then(function() {
        def.resolve();
    });

    return def.promise();
}

function test(x){
    var def = $.Deferred();
  test1(x).done(function(){
                setTimeout(function(){ console.log('processed test item', x); def.resolve();}, 1000);
          });
  return def.promise();
}

function test1(items){
    var _d = $.Deferred();
  setTimeout(function(){ 
    console.log('processed test1 item', items); 
    _d.resolve();
  });
  return _d.promise();
}

main().done(function(){ console.log('completed')});

Code contains a main function which executes loop. On each loop, a sub function(test) is executed. Inside the sub function(test) another function(test1) is called. Both sub functions test and test1 has AJAX call declaration. For AJAX call I have used setTimeout property. I'm expecting an output like

processed test1 item0
processed test item0
processed test1 item1
processed test item0
completed

For each loop, I want the function to be executed as Test1() then test(); However I'm getting the result as

processed test1 item 0
 processed test1 item 1
processed test item 0
processed test item 1
completed

After executing the test1 completely test function is executed. Why the function is not executing sequentially for each loop.

UPdated code for another test run

function main(items) {
    var items = items;
    return items.reduce(function (p, index) {
        return p.then(function () {
            return test(index);
        });
    }, $.Deferred().resolve());
}

function test(x) {
    var def = $.Deferred();
    test1(x).done(function () {
        setTimeout(function () {
            log('processed test item', x);
            def.resolve();
        }, 1000);
    });
    return def.promise();
}

function test1(items) {
    var _d = $.Deferred();
    setTimeout(function () {
        log('processed test1 item', items);
        _d.resolve();
    });
    return _d.promise();
}

var items = [0, 1];
function test2(x) {
    var _d = $.Deferred();
    setTimeout(function () {
        log('processed test2 item',x);
        _d.resolve();
    });
    return _d.promise();
}

main([1,2]).done(function(data){test2(items);}).then(function () {
    log('completed')
});
<script src="https://dl.dropboxusercontent.com/u/7909102/log.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

why 'completed' is logged before processing test2 function?

Community
  • 1
  • 1
Darknight
  • 1,132
  • 2
  • 13
  • 31
  • Please include your code directly pasted into your question. Questions that rely only on external references for code are not allowed here and external references tend to change or disappear rendering the question useless as a future reference to others. – jfriend00 Jul 19 '16 at 21:13

1 Answers1

2

Your result is as expected.

Your for loop runs synchronously to completion and runs test() twice.

test() then immediately calls test1() so the first thing you see is that test1() gets to run twice. Then, after each test1() completes its promise, it sets the timer for your test() log message. So naturally, the two log messages from test() comes after the two log messages from test1().

Remember that $.when() runs things in parallel so all the promises you pass it are in flight at the same time.

If you want to serialize your calls to test(i) so the next one doesn't happen until after the first one, then you need to do things differently.

Also, you are using an anti-pattern in main() by creating a deferred where you don't need to create one. You can just return $.when.apply(...). You don't need to wrap it in another deferred.


To serialize your calls to test(i) to get the type of output you indicate you wanted, you can do this:

function main() {
    var items = [0, 1];
    return items.reduce(function(p, index) {
        return p.then(function() {
            return test(index);
        });
    }, $.Deferred().resolve());
}

Working demo that generates your desired output: https://jsfiddle.net/jfriend00/hfjvjdcL/

This .reduce() design pattern is frequently used to serially iterate through an array, calling some async operation and waiting for it to complete before calling the next async operation on the next item in the array. It is a natural to use .reduce() because we're carrying one value through to the next iteration (a promise) that we chain the next iteration to. It also returns a promise too so you can know when the whole thing is done.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • I tried your sample fiddle, its working as expected. However if I try to add a new ajax call after main function. its not working as expected.https://jsfiddle.net/fewtalks/43bbzbeh/. Log should print "processed test2 item" before "completed" – Darknight Jul 20 '16 at 06:25
  • @fewtalks - As always, I can help with code I can see. Can't help with code you don't show. If you show the exact code you are having trouble with, I can take a look. – jfriend00 Jul 20 '16 at 06:26
  • updated new code in question section. replace log with console.log() – Darknight Jul 20 '16 at 06:29
  • @fewtalks - Stop using jQuery's brain-dead `.done()` and use `.then()` which properly chains per the promise specification (jQuery's 1.x and 2.x have other non-standard issues with `.then()`, but at least it chains properly). See https://jsfiddle.net/jfriend00/7666yrg0/. – jfriend00 Jul 20 '16 at 06:38
  • passing arguments to deferred funciton is not working as expected. updated code in question. – Darknight Jul 20 '16 at 07:31
  • @fewtalks - This is feeling a little bit like a never ending process here and that's not really how stack overflow is supposed to work. You may need to start asking new questions for new issues. Your latest example fails to return the promise from the anonymous function that is your `.then()` handler. Change this: `main([1,2]).done(function(data){test2(items);}).then(...)` to this: `main([1,2]).then(function(data){return test2(items);}).then(...)`. If you want a promise chain to wait for an async operation inside the `.then()` handler, then you must return a promise from the `.then()` handler. – jfriend00 Jul 20 '16 at 07:37
  • i couldnt find a good tutorial on jquery deferred concepts. Thats why. – Darknight Jul 20 '16 at 07:44
  • @fewtalks - The majority of stuff is written about generic and standard promises of which jQuery is a quirky implementation with its own non-standard ideas about how to do a few things, but same general concept. You can create more new questions here about appropriate topics. But stack overflow isn't meant to be continuous stream of questions followed by more questions. It's meant to be one well thought through question that is the followed by one or more clear answers to that question. Further questions that flow out of the original understanding really belong in new questions. – jfriend00 Jul 20 '16 at 07:57