2

Ok, so I've read 1,000,000+ articles on jQuery deferreds and/or promises, and I'm still getting something wrong.

functionOne() {
    var deferred = $.Deferred();

    var request = $.ajax({
        url: 'http://example.com/mypath/etc'
    });

    request.done(function(data) {

        // TODO: I've got stuff here that takes a while.

        deferred.resolve();
    });

    return deferred.promise();
}

functionTwo() {
    // Something that depends on the others being finished.
}

$.when(
    functionOne(), 
    anotherLongRunningFunctionWithAjax())
 .then(
    functionTwo()
);

I need any function(s) in the "when" to fully complete (.ajax done) before the "then" starts. However, the promise returns immediately (as expected), but functionTwo starts, even though functionOne has not called "done".

I'm sure it's a fundamental misunderstanding of deferred and the chain-of-calls.

Edit:

function functionOne() {
    console.log('functionOne called');

    var request = $.ajax({
        url: 'http://example.com/mypath/etc'
    });

    request.done(function(data) {
        console.log('Starting Done.');

        setTimeout(function () {
            console.log('Done finished.');
        }, 5000);
    });

    console.log('Returning promise.');
    return request;
}

function functionTwo() {
    console.log('functionTwo called');
}

$.when(functionOne()).then(functionTwo());

Gives me this in the console:

functionOne called
Returning promise.
functionTwo called (should be called after Done is finished.)
Starting Done.
Done finished.
  • 1
    `var deferred = $.Deferred();` unnecesary, you already get one from $.ajax. Chain off of it with .then. – Kevin B Feb 03 '17 at 20:20
  • Your logic as-is should work, my guess is the `// TODO:` sections actually have code in them and aren't delaying the .resolve() call properly. – Kevin B Feb 03 '17 at 20:21
  • @KevinB Quite honestly, I tried it without the additional $.Deferred first, but I couldn't get that to work either. I'm sure I'm missing something simple. –  Feb 03 '17 at 20:55
  • Your new code after update will not delay function 2 on function 1's setTimeout completion, that's by design. For that functionality, you'd have to use .then rather than .done and create a new promise to return to the .then that resolves when the setTimeout completes. Additionally, $.when isn't needed for a single promise, but it doesn't necessarily hurt either. – Kevin B Feb 03 '17 at 21:01
  • @KevinB But whether I'm using setTimeout or something else, wouldn't the "Starting done" at least occur? I'm not actually using a setTimeout, but loading a dropdown. In fact, you can completely comment-out the setTimeout, and the "Starting done" is still occuring last. –  Feb 03 '17 at 21:06
  • well, no, because .done() doesn't alter the promise, it just attaches a callback to it. callbacks are first in first out. – Kevin B Feb 03 '17 at 21:08

2 Answers2

3

Taking the code in your edit, there are two issues:

  1. The timer in functionOne starts after request is resolved, yet you return request. So whatever happens with the timer... it is of not relevance to the returned promise, which at that time is already resolved.

  2. You call functionTwo immediately, instead of passing the function reference for the $.when promise to call back

Here is working code:

function functionOne() {
    console.log('functionOne called');
    console.log('Returning promise.');
    return $.ajax({
        url: 'https://jsonplaceholder.typicode.com/posts/1'
    }).then(function(data) {
        console.log('Starting Done.');
        var dfd = $.Deferred();
        setTimeout(function () {
            console.log('Done finished.');
            dfd.resolve(data); // indicate when you are done
        }, 2000); // 2 seconds will do ;-)
        return dfd.promise(); // you need to return a promise again
    });
}

function functionTwo() {
    console.log('functionTwo called');
}

// don't call functionTwo immediately, leave that to the promise to do:
$.when(functionOne()).then(functionTwo);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
trincot
  • 317,000
  • 35
  • 244
  • 286
  • 1
    Also, for future reference (for myself): If I add parenthesis to functionTwo in "then" (e.g. .then(function())), the function will still fire immediately. Therefore, if params are needed in functionTwo, create a var holding the function pointer. e.g. var f2 = function() { functionTwo(param); } and pass the pointer to ".then(f2)". Or, .then(function() { functionTwo(param); }); –  Feb 03 '17 at 22:13
  • 2
    For adding parameters, you can also use `bind`: `.then(functionTwo.bind(null, param))`. – trincot Feb 03 '17 at 22:19
1

you are using an anti-pattern since $.ajax itself returns a promise

Just do

functionOne() {

    var request = $.ajax({
        url: 'http://example.com/mypath/etc'
    });

    request.done(function(data) {
        // TODO: I've got stuff here that takes a while.       
    });

    return request
}
Community
  • 1
  • 1
charlietfl
  • 170,828
  • 13
  • 121
  • 150