Jenkov.com
Tutorials About




jQuery Deferred Objects


Deferred objects in jQuery represents a unit of work that will be completed later, typically asynchronously. Once the unit of work completes, the deferred object can be set to resolved or failed.

A deferred object contains a promise object. Via the promise object you can specify what is to happen when the unit of work completes. You do so by setting callback functions on the promise object. I will show you how all that works later in this text.

Here is a diagram that shows how jQuery's deferred objects work. Don't worry if you do not understand it all now. It will be explained later. Then you can return to this diagram after reading the explanation.

How jQuery deferred objects work

Typical Deferred Object Use Cases

Deferred objects are typically used wherever you would normally pass a call back function to be executed when some asynchronous unit of work completes. The most common cases when asynchronous work is being executed are:

  • AJAX calls
  • Loading resources over the network
  • setTimeout() or setInterval()
  • Animations
  • User interaction

When an AJAX call is executed it will be executed asynchronously by the browser. Once the AJAX call completes some callback function is called by the browser.

When loading e.g. images over the network with JavaScript you can specify a callback function to call when the image is fully loaded. Loading images is very similar in nature to AJAX calls. Both are HTTP requests sent over the network.

When you use the JavaScript setTimeout() and setInterval() functions you pass along some callback function to be executed when the timeouts occur.

Animations usually run for a certain amount of time. When an animation finishes you might want to perform some actions, either to clean up after the animation, or some other actions related to the animation. For instance, if you animate the removal of an item in a list, you might want to finish up by actually removing the item from the list in your data structures or in the DOM too.

User interaction is another typical asynchronous situation. Your code may open a dialog and specify what is to happen when the dialog is closed with a press on the "OK" button, or with the "CANCEL" button. Sometimes you will work around this asynchronous situation by setting explicit listener functions on the "OK" and "CANCEL" buttons, instead of passing callbacks to the function that opens the dialog. Both are viable option though.

Using jQuery's Deferred Objects

Before showing how deferred objects look in use, let me first show you how a standard asynchronous function looks without deferred objects. The following example function takes a callback function as parameter which is executed after a certain time delay.

function doAsync(callback){
    setTimeout(function() { callback(); }, 1000);
}

Calling the doAsync() function with a callback would look like this:

doAsync(function() {
    console.log("Executed after a delay");
});

After the delay the function passed as parameter to doAsync() will be executed.

The doAsync() function could also be implemented using a jQuery deferred object. Here is a jQuery deferred object example implementation of the doAsync() function (called doAsync2()):

function doAsync2() {
    var deferredObject = $.Deferred();

    setTimeout(function() { deferredObject.resolve();  }, 1000);

    return deferredObject.promise();
}

First a deferred object is created using the jQuery $.Deferred() function. The deferred object does not do anything by itself. It is just a representation of some asynchronous work.

Second, a function is passed to the setTimeout() function. This function gets executed after a delay. The function calls deferredObject.resolve(). This causes the internal state of the deferred object to change to resolved (after the delay).

Finally, the promise object associated with the deferred object is returned. Through the promise object the code calling the doAsync2() function can decide what should happen if the deferred object's state changes to resolved or failed.

Calling the doAsync2() function would look like this:

var promise = doAsync2();

promise.done(function () {
    console.log("Executed after a delay");
});

Notice how the callback function is no longer passed as parameter to the doAsync2() function. Instead, the callback function is passed to the done() function on the promise object. Once the deferred object's state changes to resolved, the callback function passed to done() will get executed.

If the deferred object's state changes to failed then the callback function passed to done() will not get executed. Instead the callback function passed to the promise object's fail() function will get executed. Here is how you pass a callback function to the fail() function:

var promise = doAsync2();

promise.done(function () {
    console.log("Executed after a delay");
});

promise.fail(function () {
    console.log("Executed if the async work fails");
});

Furthermore, since the done() and fail() functions return the promise object itself, you could shorten the above code to:

doAsync2()
  .done(function () {
    console.log("Executed after a delay");
}).fail(function () {
    console.log("Executed if the async work fails");
});

Of course, the implementation of the doAsync2() function never sets the state of the deferred object to failed, so this function will never get executed in this example. Let us change the implementation of doAsync2() (as doAsync3()) so that the deferred object may actually change state to failed.

function doAsync3() {
    var deferredObject = $.Deferred();

    setTimeout(function() {
        var randomValue = Math.random();
        if(randomValue < 0.5) {
            deferredObject.resolve();
        } else {
            deferredObject.reject();
        }
    }, 1000);

    return deferredObject.promise();
}

In this implementation the asynchronously called function generates a random number between 0 and 1. If the number is less than 0.5 the deferred object's state is set to resolved. Otherwise the deferred object's state is set to failed. This is done by calling the deferred object's reject() function.

The Deferred Object vs. The Promise Object

If you look at the jQuery documentation you will see that a deferred object also has a done() and fail() function, just like the promise object has. That means, that instead of returning the promise object from the deferred object, you could return the deferred object itself. Here is how that would look:

function doAsync4() {
    var deferredObject = $.Deferred();

    setTimeout(function() {
        var randomValue = Math.random();
        if(randomValue < 0.5) {
            deferredObject.resolve();
        } else {
            deferredObject.reject();
        }
    }, 1000);

    return deferredObject;
}

The only difference between doAsync4() and doAsync3() is the last line. Instead of returning deferredObject.promise() it returns deferredObject.

There is no difference in how you would use the doAsync4() and doAsync3(). You can call done() and fail() on the deferred object directly too. The effect is the same as calling these functions on the promise object.

The main difference between returning the deferred object and returning the promise object is that the deferred object can be used to set the resolved or failed state of the deferred object. You cannot do that on the promise object. So, if you do not want the user of your asynchronous function to be able to modify the state of the deferred object, it is better to not return the deferred object but only its promise object (deferredObject.promise()).

The Promise Object

The promise object returned by a deferred object contains the following functions you can call:

  • done()
  • fail()
  • always()
  • progress()
  • then()
  • state()

The two first functions, done() and fail() we have already looked at. They are called when the deferred object connected to the promise object changes its state to either resolved or failed.

The always() function, also takes a callback function as parameter. This callback function is always called when the deferred object changes state to either resolved or failed. It does not matter which state the deferred object changes to. When the state changes, first either the callback passed to done() or fail() is called, and then the callback passed to always() is called.

The progress() function can be used to attach a progress callback function to the promise object. This callback function is called whenever the notify() function is called on the deferred object connected to the promise object. This system can be used to notify a listener about the progress of a long running asynchronous operation. I will show you an example of how to do that later in this text.

The then() function allows you to chain and filter promise objects. You can also just use it as a shortcut to set callback functions for the resolved and failed state changes, instead of using done(), fail() and progress(). I will show how to use the then() function later in this text.

The state() function returns the state of the deferred object connected to the promise object. It will return one of the strings "pending", "resolved" or "rejected". The string "pending" represents the state of a deferred object which has not yet been resolved or rejected.

Attaching Multiple Callbacks to a Promise Object

You can attach more than one callback function for each deferred object state to a promise object. Here is an example:

    var promise = doAsync3();

    promise.done(function() { console.log("done 1"); } );
    promise.done(function() { console.log("done 2"); } );

    promise.fail(function() { console.log("fail 1"); } );
    promise.fail(function() { console.log("fail 2"); } );

    promise.always(function() { console.log("always 1"); } );
    promise.always(function() { console.log("always 2"); } );

This example attaches 2 callbacks using each of the done(), fail() and always() functions. The callback function will be called in the order they are registered, when the deferred object changes its state.

Resolving or Rejecting a Deferred Object

You can resolve or reject a deferred object using the deferred object's resolve() and reject() functions. When you do so, any callback functions added via the promise object of the deferred object, or via the deferred object itself, will get called. You have actually seen an example of this works earlier, but I will repeat it here:

function doAsync3() {
    var deferredObject = $.Deferred();

    setTimeout(function() {
        var randomValue = Math.random();
        if(randomValue < 0.5) {
            deferredObject.resolve();
        } else {
            deferredObject.reject();
        }
    }, 1000);

    return deferredObject.promise();
}

As you can see, the deferred object's resolve() and reject() functions are called from inside the delayed function passed to setTimeout().

You can actually pass values to the resolve() and reject() functions. These values will then be passed on to the callback functions registered via the done(), fail() and always() functions.

Here is doAsync4() rewritten to show how to pass parameters via resolve() and reject():

function doAsync4() {
    var deferredObject = $.Deferred();

    setTimeout(function() {
        var randomValue = Math.random();
        if(randomValue < 0.5) {
            deferredObject.resolve( randomValue, "val2" );
        } else {
            deferredObject.reject(  randomValue, "errorCode" );
        }
    }, 1000);

    return deferredObject;
}

When registering the callback functions you should make the callback functions take the passed values as parameters. Here is an example:

doAsync4().done(function (value, value2) {
    console.log("doAsync4 succeeded: " + value + " : " + value2);

}).fail(function(value, value2) {
    console.log("doAsync4 failed: " + value + " : " + value2);

}).always(function(value, value2) {
    console.log("always4: " + value + " : " + value2);
});

As you can see, the three callback functions all take two parameters.

You can pass as many parameters as you like via resolve() and reject().

Monitoring Progress of Deferred Objects

If an asynchronous process takes a long time to complete, you can monitor the progress of the process. It requires that the asynchronous process notifies potential listeners about the progress of the long running process. This can be done via the deferred object notify() function. Here is an example showing how to call the notify() function:

function doAsync5() {
    var deferredObject = $.Deferred();

    setTimeout(function() { deferredObject.notify(1); }, 1000);
    setTimeout(function() { deferredObject.notify(2); }, 2000);
    setTimeout(function() { deferredObject.notify(3); }, 3000);
    setTimeout(function() { deferredObject.resolve(); }, 4000);

    return deferredObject.promise();
}

Notice how the doAsync5() function sets up four delayed functions. The first 3 delayed functions call deferredObject.notify() with different parameter values. The final delayed function resolves the deferred object.

Calling notify() on a deferred object results in calling the callbacks added via the progress() function on the associated promise object. Here is how you would use the progress() function on the promise object to be notified of the notify() calls on the deferred object:

doAsync5().progress(function(progressValue) {
    console.log("doAsync5 progress : " + progressValue)
}).done(function () {
    console.log("doAsync5 succeeded. ");
});

Notice the progress() call on the promise object returned by doAsync5(). The callback passed to progress() will get called whenever the notify() function is called on the deferred object connected to the promise object. Whatever parameters you pass to notify() will get forwarded to the callback function registered with progress().

Chaining and Filtering Promise Objects

The promise object has function called then() which can be used to chain promise objects. Here is a chained promise example using then() :

doAsync5().then(function(val) { console.log("done 1") },
                function(val) { console.log("fail 1") },
                function(val) { console.log("prog 1") }

         ).then(function(val) { console.log("done 2") },
                function(val) { console.log("fail 2") },
                function(val) { console.log("prog 2") }
         );

The then() function takes three parameters. The first parameter is the function to call if the deferred object is resolved (done). The second function is the function to call if the deferred object is rejected (fail). The third function is a progress function which is called whenever the asynchronous code calls notify() on the deferred object.

The code in the above example is similar to this code:

doAsync5()
    .done(function(val) { console.log("done 1") })
    .fail(function(val) { console.log("fail 1") })
    .progress(function(val) { console.log("prog 1") })

    .done(function(val) { console.log("done 2") })
    .fail(function(val) { console.log("fail 2") })
    .progress(function(val) { console.log("prog 2") })
;

Both versions set up two sets of listener functions listening for the deferred object state change (resolved / rejected) and progress notifications. There is one significant difference though.

In the last shown version using done(), fail() and progress() the value passed to each listener is the exact value as passed to resolve(), reject() and notify() on the deferred object.

In the first version using then(), each listener function has the option to change (filter) the value passed on to listeners registered later than itself. It does so by returning another value. The value returned by a listener will be the value returned to the next listener of that type. Thus, a resolved listener can filter the value passed on to subsequent resolved listeners. A progress listener can filter the value passed on to subsequent progress listeners. And the same for rejected (fail) listeners. Look at this example:

doAsync5().then(function(val) { console.log("done 1: " + val); return val + 1; },
                function(val) { console.log("fail 1: " + val) },
                function(val) { console.log("prog 1: " + val); return val + 1; }

         ).then(function(val) { console.log("done 2: " + val) },
                function(val) { console.log("fail 2: " + val) },
                function(val) { console.log("prog 2: " + val) }
         )

In this example the first done and progress listeners return the original value passed to them incremented by 1 (val++). The incremented value returned will become the parameter value passed to the next listener function of the same kind. This filtering effect can be used to create advanced promise listener chains.

Combining Promises With $.when()

If you need to wait for more than one deferred object to complete, you can combine their promise objects using the $.when() function. Here is an example:

$.when(doAsync4(), doAsync4())
        .done(function(val1, val2) {
            console.log("val1: " + val1);
            console.log("val2: " + val2);
        })
        .fail(function(val1, val2) { 
            console.log("fail: " + val1 + " : " + val2 + " : " + val3);
        })
;

This example calls the doAsync4() function two times, and combine the promise object returned by the two function calls using the $.when() function.

The $.when() function returns a new promise object on which you can attach callback functions using done(), fail(), always() etc. If all promises passed as parameters to the $.when() call succeed (= have resolve() called on their deferred objects), then the callbacks set via done() on the promise returned by $.when() will get called. If a single of the promise objects passed to $.when() fails, then the callbacks added via fail() will get called. The $.when() function thus works like a logical and function. All input promises must succeed for the promise returned by $.when() to succeed too.

The parameter values of callback functions registered on the promise returned by $.when() have a slightly different semantic meaning than the parameters of a normal promise object. On a normal promise object the parameters passed to a done() callback are the parameters passed to the deferred object's resolve() function. However, when you combine two promise objects together, the done() listener receive the parameters passed to the resolve() calls of both deferred objects.

There are two scenarios to take into consideration when combining resolve() parameters. The first scenario is where all calls to resolve() take a single value. In that case each value passed to a resolve() call is passed as a separate parameter to the callback registered via the done() function on the promise object returned by the $.when() function. Here is an example of how accessing the resolve() parameters in that scenario looks:

$.when(doAsync4(), doAsync4())
        .done(function(val1, val2) {
            console.log("val1: " + val1);
            console.log("val2: " + val2);
        })
        .fail(function(val1, val2) {
            console.log("fail: " + val1 + " : " + val2 + " : " + val3);
        })
;

The val1 parameter will receive the value from the resolve() call on the first deferred object passed to $.when(). The val2 parameter will receive the value from the resolve() call on the second deferred object passed to $.when().

In case one or both of the resolve() calls receive more than one parameter value, then the meaning of val1 and val2 in the example above changes. Instead of being parameter values themselves, the become arrays of parameter values from the first and second resolve() calls. In that case you access the parameter values like this:

$.when(doAsync4(), doAsync4())
        .done(function(val1, val2) {
            for(var i=0; i < val1.length; i++) {
                console.log("val1[" + i +"]: " + val1[i]);
            }
            for(var i=0; i < val2.length; i++) {
                console.log("val2[" + i +"]: " + val2[i]);
            }
        })
        .fail(function(val1, val2) {
            console.log("fail: " + val1 + " : " + val2 + " : " + val3);
        })
;

Notice how both val1 and val2 are now arrays.

In case one of the deferred objects fail, then the parameters of the failing deferred object's reject() call will be passed to the callback registered using fail() on the promise object returned by $.when(). Regardless of whether one or more deferred object fails, you only get the parameters from the first failed deferred object's reject() call as parameters to the callback registered with fail() on the promise returned by $.when() .

Deferred Object Advantages Over Callback Functions

Deferred objects have a few advantages over passing callback functions as parameters to asynchronous functions.

The ability to attach more than one callback function is one advantage that deferred objects and promise objects give compared to a function that simply takes a single callback function. Of course, an asynchronous function could have just taken an array of callback functions, but to equal the promise object in power, each asynchronous function would have to take an array of "done" callbacks, and array of "fail" callbacks, and an array of "always" callbacks. Having to take three such arrays and calling these callbacks would lead to clumsy function implementations. It is much cleaner to have that functionality gathered in one place - in the deferred object and its promise object.

In addition to cleaning up asynchronous functions internally, deferred objects also clean up the external code that calls the asynchronous functions. For instance, instead of having to nest callbacks inside callbacks to wait for more than one asynchronous action to complete, the $.when() function combines two or more promises nicely.

Third, once you understand deferred objects and promise objects, you pretty much understand all functions that use these constructs. You understand how to handle return values, how to handle failures, monitor progress if the asynchronous function offers that etc.

Deferred Objects in jQuery

jQuery uses deferred objects and promises internally in some of its APIs. For instance, when you call the $.ajax() function it returns a jqXHR object. Among the functions available on the jqXHR object are the three promise functions, done(), fail() and always(). That means, that even if the jqXHR object is much more than just a promise object, you can use it as a promise object.

Promise objects can also be used in conjunction with animation in jQuery. The following example is modified version of an example jQuery documentation:

$( "#theButton" ).on( "click", function() {

   $( "div" ).each(function( i ) {
       $( this ).fadeIn().fadeOut( 1000 * ( i + 1 ) );
   });

   $( "div" ).promise().done(function() {
       $( "p" ).append( " Finished! " );
   });
});

When the button with id theButton is clicked, all div elements are faded in. Second, a promise object is obtained from the collection of all div elements. This promise object will have its done() callbacks called when animations of all selected div elements finish. It is a bit tricky semantic meaning, that the promise object obtained from a selection of elements is bound to their animations, I will grant you that.



Connect with me: Newsletter - Get all my free tips!
     



Close TOC
This website uses cookies to improve the user experience and gather statistics. Our advertisers use cookies too (3rd party cookies), to provide more relevant ads. Continued use of this website implies that you accept the use of cookies on this website. We do not share our cookies with our advertisers, and our advertisers do not share cookies with us.
OK