0

I have a bunch of nested functions returning deferred objects from ajax calls. Here's what my code looks like

function makeCalls() {
    var ajaxDfd1 = $.ajax(...);

    ajaxDfd1.then(function() {
        // want to execute after first call
        var ajaxDfd2 =  $.ajax(...);

        ajaxDfd2.done(function() {
            // want to execute after second call
        });

        return ajaxDfd2;
    });

    return ajaxDfd1;
}

makeCalls().done(function() {
    // stuff here was executed early
});

But my calls are not being executed in the order I intend them to be. Stuff inside makeCalls().done() seems to be called before ajaxDfd2 is actually done.

Charlotte Tan
  • 2,452
  • 2
  • 20
  • 24

3 Answers3

2

Choc,

You've made an incorrect assumption in your answer; namely that $.ajax() returns a "regular object".

This is not correct.

$.ajax() returns a jqXHR object, which is a superset of a Promise, ie. it has all of Promise's methods plus others which are specific to AJAX (such as .abort()).

If the object returned by $.ajax() was not Promise-compatible, then the code in your question would throw an error on trying to execute makeCalls().done(...).

The reason why your .done() function executes first it that the function returns the outer jqXHR.

Here's a couple of ways to achieve the behaviour you were expecting :

function makeCalls() {
    var dfrd = new Deferred();
    $.ajax(...).then(function() {
        $.ajax(...).done(dfrd.resolve);
    });
    return dfrd.promise();
}

makeCalls().done(function(data) {
    // ...
});

or

function makeCalls(fn) {
    $.ajax(...).then(function() {
        $.ajax(...).done(fn);
    });
}

makeCalls(function(data) {
    // ...
});

You could even try :

function makeCalls() {
    return $.ajax(...).then(function() {
        return $.ajax(...);
    });
}

makeCalls().done(function() {
    // ...
});
Beetroot-Beetroot
  • 18,022
  • 3
  • 37
  • 44
  • I would not recommend either of the first two versions. The last one is perfectly fine. – Bergi Feb 22 '13 at 23:25
  • @Bergi, I'm sure you are about to explain why! – Beetroot-Beetroot Feb 22 '13 at 23:32
  • Because the first is overly complicated - `then` (which you are already using) does exactly what you are doing manually; and because the second does not use Deferreds at all but callbacks which was not asked for (Promises were made to supersede CPS) – Bergi Feb 22 '13 at 23:50
  • 1
    @Bergi, OK fair comments though I think it's good for a learner to be exposed to a range of possibilities, each of which still has its place in real-world coding. – Beetroot-Beetroot Feb 23 '13 at 00:56
0

I knew I must be getting something wrong, and spent a really long time staring at this: When should I use jQuery deferred's "then" method and when should I use the "pipe" method?

Later I realised that I can't do

var dfdObj = $.ajax(...);
dfdObj.then(...);

return dfdObj;

This doesn't actually return the deferred object. It returns an Ajax object, and since it is a regular object, it is automatically classified as resolved and thus the .done() function was being called early. Instead, I need to do this if I want to return a var

var dfdObj = $.ajax(...).then(...);

return dfdObj;
Community
  • 1
  • 1
Charlotte Tan
  • 2,452
  • 2
  • 20
  • 24
0

Your code works like this:

function makeCalls() {
    var ajax1 = $.ajax(…);
    var ajax1and2 = ajax1.then(makeCall2);
    function makeCall2() {
        // want to execute after first call
        var ajax2 =  $.ajax(…);
        ajax2.done(after2);
        function after2() {
            // want to execute after second call
        });
        return ajax2;
    });
    return ajax1;
}

var ajax1 = makeCalls();
ajax1.done(function() {
    // stuff here was executed early
});

ajax1and2 is the result of the then/pipe call you wanted. A callback on it will wait for the result of makeCall2 (i.e. the ajax2) which waits for ajax1. It would be equal to the after2 callback.

Yet, your makeCalls function returns the ajax1 object (you can see I assigned it to a global ajax1 variable as well) and both the makeCall2 and the done-callback will be executed after your first ajax has finished.

So, instead you need to return the ajax1and2 deferred:

function makeCalls() {
    return $.ajax(…).then(function() {
        return $.ajax(…).done(function() {
            // want to execute after second call
        });
    });
}

makeCalls().done(function() {
    // stuff here is now executed after second call as well
});
Bergi
  • 630,263
  • 148
  • 957
  • 1,375