0

Despite the many answers here on the topic, I still don't really understand $.Deferred() objects.

I want to understand them better and avoid any "anti-patterns".

I have read:

https://api.jquery.com/category/deferred-object/

And what "sticks" is that it is an object with methods available to it, such as done().

I've tried to create the most simple example possible, based on this answer:

jsFiddle Link

function function1() {
    // create a deferred object first
    var dfrd1 = $.Deferred();

    // mimic an async request
    setTimeout(function() {
        // lots of async stuff here
        // resolve the deferred object 
        dfrd1.resolve();
    }, 1000);

    // return a promise()
    return dfrd1.promise();
}

function1().done(function() {
    // when function1 is done, do something
    console.log('function1 is done!');
});

There seem to be four components to this:

  • Create a $.Deferred() object.
  • resolve() it after the asynchronous block of code.
  • Return a promise() after the asynchronous block of code.
  • Call the done() method.

The above example seems to work as expected, however when I try to apply this logic to a large function that contains an $.ajax() request, in order to apply a class to content after it has loaded, the class is not being applied.

I can run the addClass() code via firebug, so there is nothing wrong with that code.

The pseudo-code for the large function scenario is:

function largeFunction(myVar) {

    // create a deferred object first
    var dfrd1 = $.Deferred();

    // lots of code and an ajax request here

    //  resolve the object and return a promise
    //  (this seems naive to hope resolve will
    //  know when everything above has completed)
    dfrd1.resolve();
    return dfrd1.promise();

}

// call the done() method from an on click event

$(document).on("click", ".some_class", function() {

    largeFunction(myVar).done(function() {

        console.log('largeFunction(myVar) is done, you can now add class');

        var my_classes = $(".my_class");

        $.each(classes, function(index, value) {
            $(this).addClass("another_class");
        });

    });

});

The following is a good, short, simple answer to a $.Deferred() question, which I understand the logic of, but doesn't seem to be applicable in my scenario (eg I can't just chain the promise() after a simple fadeOut()):

https://stackoverflow.com/a/24234881/1063287

Question

In the large function example above, what conventions do I need to follow in order to call done() after the ajax request etc has finished executing?

Community
  • 1
  • 1
user1063287
  • 10,265
  • 25
  • 122
  • 218

1 Answers1

1

First off, I'd suggest changing this:

function function1() {
    // create a deferred object first
    var dfrd1 = $.Deferred();

    // mimic an async request
    setTimeout(function() {
        // lots of async stuff here
        // resolve the deferred object 
        dfrd1.resolve();
    }, 1000);

    // return a promise()
    return dfrd1.promise();
}

function1().done(function() {
    // when function1 is done, do something
    console.log('function1 is done!');
});

to this:

function function1() {
    // create a deferred object first
    return $.Deferred(function(def) {
        // mimic an async request
        setTimeout(function() {
            // lots of async stuff here
            // resolve the deferred object 
            def.resolve();
        }, 1000);
    }).promise();

}

function1().then(function() {
    // when function1 is done, do something
    console.log('function1 is done!');
});

Your original code works fine and contains no anti-patterns, but this new code has these advantages:

  1. Using the callback to $.Deferred() is closer to how the ES6 promise standard works with new Promise() so it's generally a good idea to move your programming in the direction of the standards.

  2. Use .then() instead of .done(). Again, .then() is how the ES6 promise standards work and jQuery supports it just fine. If at some point in the future, you changed function1() to use an actual ES6 promise instead of jQuery promise, then your function1().then() would continue to work just fine.


In jQuery, an Ajax request already returns a promise. There is no need to wrap it in a deferred and, in fact, doing so is an anti-pattern (creating a promise when you don't need to create a new one).

Promises have NO magic powers to just know when asynchronous things are done. Instead, some code has to specifically call .resolve() when the asynchronous operation is done. And, in the case of jQuery Ajax functions, this is done automatically for you with the promise that any of the jQuery Ajax functions return. So, you can just do this:

function largeFunction(myVar) {

     return $.ajax(...);

}

largeFunction(....).then(function(results) {
    // handle successful results here
}, function(jqXHR, textStatus, errorThrown ) {
    // handle error here
});

If you have multiple Ajax functions or asynchronous operations inside largeFunction(), then you can use promises to chain them (which will sequence them) or coordinate them and still return a single promise that represents when everything is done and represents the desired final results. jQuery provides coordination tools such as $.when() to know when multiple promises are done (this is Promise.all() in the ES6 standard).

jfriend00
  • 683,504
  • 96
  • 985
  • 979