The jQuery Deferred Object

jQuery’s implementation adds onto the basic premise of the promise with several useful enhancements. Importantly, it also integrates directly with one of the core features of the library, $.ajax, so Deferred opens up a world of possibilities once you’ve got your head wrapped around them.

As is the jQuery way, the format of the implementation is heavy on the ability to chain methods in a convenient way. It’s also written in a friendly, human-readable style.

This section looks at jQuery’s basic implementation and walks through some of the more advanced features available.

1. $.when, Deferred.done, and Deferred.fail

When working with Deferreds, you’ll find that a lot of activity flows through the new jQuery method $.when. $.when accepts either one or more Deferred objects (which importantly include $.ajax calls) or a plain object.

If a Deferred is passed to $.when, its promise object is returned by the method. You see more about how to manage jQuery’s promises in a later section, but additional methods are then available to chain and structure callbacks.

The first two methods are Deferred.done and Deferred.fail.

Deferred.done accepts a single function or array of functions to fire when a promise is resolved successfully.

Deferred.fail also accepts a function or array of functions. It fires when a promise is rejected.

As a note, if an argument is passed to $.when and it is not a Deferred, it is treated as a resolved Deferred. If no other Deferred objects are passed to $.when, then Deferred.done fires.

The following code presents a bare-bones jQuery Deferred example using $.when, Deferred.done, and Deferred.fail.

The example starts off by defining three functions. The function fib does some math and then displays an unordered list with the results of that calculation. Two other functions are then defined, success and failure. These are simple functions that change the messaging in an hi depending on the result of the first function. The interesting piece is that one or the other will fire depending on whether the promise is resolved or rejected.

<!doctype html>

<html>

<head>

<meta charset=”utf-8″>

</head>

<body>

<div id=”main”>

<h1></h1>

<ul id=”numbers”>

</ul>

</div>

<script src=”http://code.jquery.com/jquery-1.7.1.min.js”></script>

<script> function fib() {

var inti = 0,

int2 = 1,

int3,

sequence = “<li>0</li><li>1</li>”;

for ( var i = 3; i <= 100; i++ ) {

int3 = int1 + int2;

int1 = int2;

int2 = int3;

sequence += “<li>”+int3+”</li>”

}

$( “#numbers” ).append( sequence ).show( 500 );

}

function success() {

$( “h1” ).text( “Fibonacci!” );

}

function failure() {

$ (“h1” ).text( “No numbers?” );

}

$(function(){

$.when( fib() )

.done( success )

.fail( failure );

});

</script>

</body>

</html>

Code snippet is from basic-deferred.txt

If you’ve been paying attention, you’ll remember that if a function is passed to $.when and it isn’t a Deferred object, it’s treated as a resolved Deferred and the Deferred.done method is called. In this case, the Deferred.fail method will never fire.

So, although it’s valid, and will work basically as expected and illustrates the basic pattern, it’s missing the star of the show. The next code sample shows fib redefined to leverage the Deferred object. The function now returns Deferred.promise. The logic of the previous fib function is passed into $.Deferred as an argument.

It’s important to note the resolution of the promise as a callback to $.show. This is an important pattern discussed in depth in the next section.

<!doctype html>

<html>

<head>

<meta charset=”utf-8″>

</head>

<body>

<div id=”main”>

<h1></h1>

<ul id=”numbers”>

</ul>

</div>

<script src=”http://code.jquery.com/jquery-1.7.1.min.js”></script>

<script>

function fib() {

return $.Deferred(function() {

var int1 = 0, int2 = 1,

int3, sequence = “<li>0</li><li>1</li>”;

for (var i = 3; i <= 100; i++) {

int3 = int1 + int2;

int1 = int2;

int2 = int3;

sequence += “<li>” + int3 + “</li>”

}

$(“#numbers”)

.append(sequence)

.show(1000, this.resolve);

}).promise();

}

function success() {

$( “h1” ).text( “Fibonacci!” );

}

function failure() {

$ (“hi” ).text( “No numbers?

}

$(function(){

$.when( fib() )

.done( success )

.fail( failure );

});

</script>

</body>

</html>

Code snippet is from proper-deferred.txt

Although this is a simple example, it illustrates the power of the Deferred pattern. Callbacks in jQuery are a common source of tight coupling. They’re often defined in place, as anonymous functions with too little attention paid to abstraction.

The following animation example presents a clear illustration of how Deferred can untangle your program logic, allowing for greater code reuse and a cleaner overall architecture. Without Deferreds, callback logic for animations is tied directly to the animation call as an optional complete argument. complete is defined as a function that will be fired when the animation ends. An example of the traditional manner of doing this is shown in this first code sample:

<!doctype html>

<html>

<head>

<meta charset=”utf-8″>

</head>

<body>

<div id=”main”>

<div id=”statusbar” style=”display:none”>

</div>

</div>

<script src=”http://code.jquery.com/jquery-1.7.1.min.js”></script>

<script>

function updateStatus() {

var $update = $(“<ul />”),

$statusbar = $(“#statusbar”),

html = []; // text buffer

for ( var i = 0, test = 20; i < test; i++ ) {

html.push( “<li>status update</li>”);

}

html = html.join(“\n”); // buffer -> string $update.append(html);

$statusbar.append($update);

$statusbar.slideDown(5000, function() {

console.log(“animation is done! On to the next operation”);

});

}

$(function(){

updateStatus();

});

</script>
</body>
</html>

Code snippet is from tight-callbacks.txt

Though there’s a simple console.log in the complete argument in this example, far more complicated application data is commonly packed into similar complete callbacks.

This might be acceptable if updateStatus will only ever be used with the anonymous callback function. If it needs to be matched with other functions, it’s a different story. Pulling the logic from a callback function buried deep inside updateStatus allows for more flexibility in terms of code reuse.

Alternatively, to solve this, you could add a callback function as an argument to updateStatus, but using Deferred and returning a resolved promise is far more flexible. The following code sample shows how updateStatus could be rewritten to leverage Deferreds:

function updateStatus() {

return $.Deferred(function() {

var $update = $(“<ul />”),

$statusbar = $(“#statusbar”),

html = []; // text buffer

for ( var i = 0, test = 20; i < test; i++ ) {

html.push( “<li>status update</li>”);

}

html = html.join(“\n”); // buffer -> string

$update.append(html);

$statusbar.append($update);

$statusbar.slideDown(1000, this.resolve);

}).promise()

}

Code snippet is from decoupling.txt

With just the simple application of a Deferred, updateStatus is now much more flexible. Instead of being tied to a single callback, you can now use that same piece of code in several different flows. The upcoming code block shows several ways in which it could be used to take advantage of Deferreds.

The example contains three callback functions. The first is equivalent to the original anonymous functions passed in as a callback in the first example. The second, alternativeCallback, represents a different path to follow after the animation completes. If the same animation is running on a second page, a different piece of markup will have to be updated. The third, happyBirthday, represents a function to be called in a special case where a specific UI update would be required, a “HAPPY BIRTHDAY” message indicator, for example. Three uses of $.when follow. The first replicates the original case with a single callback function. The second shows the alternative callback. The third shows multiple callbacks.

function callback(){

console.log(“The animation is done. On to the next operation”);

}

function alternativeCallback(){

console.log(“The animation is done. Let’s follow a different path.”);

}

function specialCase(){

console.log(“This is a special case. Let’s do a special UI update.”);

}

$.when( updateStatus() )

.done( callback );

//an alternative callback

$.when( updateStatus() )

.done( alternativeCallback );

//multiple callbacks

$.when( updateStatus() )

.done(

[ callback, specialCase ]

);

Code snippet is from decoupled-callbacks.txt

As you can see, the code nicely decoupled the callback logic from the animation buried in the function body. This allows for greater code reuse and presents a much clearer pattern for any other developers on the project to grasp. No one even needs to read updateStatus to know how the application flows. It’s all laid out in (nearly) plain English.

2. Deferred.then Syntactic Sugar for Deferred.fail and Deferred.done

As with aliases for $.ajax like $.get and $.post, jQuery provides a convenient alias for the core Deferred objects: Deferred.then. Deferred.then accepts two arguments, one for resolved promises and the other for rejected promises. Like the functions it maps to, Deferred.done and Deferred .fail, the arguments for Deferred.then can either be individual functions or an array of functions.

The following code sample shows the original Deferred example from earlier in the chapter rewritten to use Deferred.then.

<!doctype html>

<html>

<head>

<meta charset=”utf-8″>

</head>

<body>

<div id=”main”>

<h1></h1>

<ul id=”numbers”>

</ul>

</div>

<script src=”http://code.jquery.com/jquery-1.7.1.min.js”></script>

<script>

function fib() {

return $.Deferred(function() {

var int1 = 0, int2 = 1,

int3, sequence = “<li>0</li><li>1</li>”;

for (var i = 3; i <= 100; i++) {

int3 = int1 + int2;

int1 = int2;

int2 = int3;

sequence += “<li>” + int3 + “</li>”

}

$(“#numbers”)

.append(sequence)

.show(1000, this.resolve);

}).promise();

}

function success() {

$( “h1” ).text( “Fibonacci!” );

}

function failure() {

$ (“h1” ).text( “No numbers?” );

}

$(function(){

$.when( fib() )

.then( success, failure );

});

</script>

</body>

</html>

Code snippet is from deferred-then.txt

Because Deferred methods are chainable, Deferred.then can be combined with Deferred.done, Deferred.fail, or even additional Deferred.then calls.

3. Using the Deferred Aspects of $.ajax

As was previously mentioned, $.ajax is a Deferred object and the success, error, and complete callback methods are analogous to Deferred.done, Deferred.fail, and Deferred.always, respectively. The following code shows a typical $.get request:

$.get(“/status/json/”,

function( data ) {

var $update = $( “<ul />” ),

$statusbar = $( “#statusbar” ), html = “”,

statusMessages = data.statusMessages;

for ( var i = 0, test = statusMessages.length; i < test; i++ ) {

html += “<li>” + status[i] + “</li>”;

}

$update.append(html);

$statusbar.append($update);

$statusbar.slideDown(1000);

}

);

Code snippet is from non-deferred-ajax.txt

The next code block shows the same Ajax request, written using Deferreds. It’s important to note that the arguments passed to updateStatus are the same that would be passed to the callback function of a typical Ajax request. It includes the data, a text string indicating the status of the request, and the jQuery XMLHttpRequest (jqXHR) object. Also introduced in jQuery 1.5 as part of the $.ajax rewrite, the jqXHR object is an enhanced version of the standard XMLHttpRequest object. It comes complete with all of the Deferred methods, which means anything that you learn about the standard Deferred object is similarly available on all $.ajax requests.

function getStatus() {

return $.ajax({

url : “/status/json/”,

  dataType :  “json”

})

}

function updateStatus( data ) {

var $update = $( “<ul />” ),

$statusbar = $( “#statusbar” ),

html = “”,

statusMessages = data.statusMessages;

for ( var i = 0, test = statusMessages.length; i < test; i++ ) {

html += “<li>” + statusMessages[i] + “</li>”;

}

$update.append( html );

$statusbar.append( $update );

$statusbar.slideDown( 1000 );

}

$.when( getStatus() )

.done( updateStatus );

Code snippet is from deferred-ajax.txt

On the surface, the preceding code may not seem like much of an improvement over the traditional callback method. It’s a little bit longer and it’s a little less free-flowing than the traditional inline callback used in jQuery Ajax requests. Beyond the surface, it’s useful to note that once again, the callback logic is decoupled from the specific Ajax request. This immediately makes that getStatus function more flexible. Additionally, as with any Deferred object, multiple $.ajax requests can be passed into $.when and multiple callbacks can be chained to respond to changes in the promise status, so the possibilities are greatly expanded. The next example presents an exaggerated, but hopefully illustrative example.

$.when(

$.post(“/echo/json”,

function() {

console.log(“1”)

}

),

$.post(“/echo/json”,

function() {

console.log(“2”)

}

),

$.post(“/echo/json”,

function() {

console.log(“3”)

}

)

).then([

function() {

console.log(“4”)

},

function() {

console.log(“5”)

},

function() {

console.log(“6”)

}

]).then([

function() {

console.log(“7”)

},

function() {

console.log(“8”)

},

function() {

console.log(“9”)}

]).then([

function() {

console.log(“10”)

},

function() {

console.log(“11”)

},

function() {

console.log(“12”)}

]).done(

function() {

console.log(“Electric Company!”)

}

);

Code snippet is from multiple-callbacks.txt

4. Execute a Function No Matter What the Promise Resolution Is with Deferred.always

Oftentimes, you want a function to be called no matter what the promise resolution is. This is analogous to the complete callback, which fires on any resolution of an $.ajax request. In the world of Deferred, the method to use is Deferred.always. Like the rest of the Deferred methods, Deferred.always takes a single function or an array of functions to fire whenever a promise is resolved or rejected. The following code shows a simplified example that changes the “last updated” indicator in a mail application.

$.when(

$.ajax( “/get/mail/” )

).done(

newMessages,

updateMessageList,

updateUnreadIndicator

).fail(

noMessages

).always(

function() {

var date = new Date();

$( “#lastUpdated” ).html( “<strong>Folder Updated</strong>: “

+ date.toDateString()

+ ” at “

+ date.toTimeString()

);

}

)

Code snippet is from always.txt

5. Chaining and Filtering Deferreds with Deferred.pipe

Deferred.pipe, introduced in jQuery 1.6, provides a method to filter and further chain Deferreds. A common use case utilizes the ability to chain Deferreds to create an animation queue. The following code shows a simplified example that uses a chained animation used to manage the build out of the components of a web application. Once the animation queue is complete, the promise is automatically resolved and the done method is fired.

function buildpage() {

return $.Deferred(function( dfd ) {

dfd.pipe(function() {

return $( ‘header’ ).fadeIn();

})

.pipe(function() {

return $( ‘#main’ ).fadeIn();

})

.pipe(function() {

return $( ‘footer’ ).fadeIn();

})

}).resolve();

}

$.when( buildpage() )
.done(function() {
console.log(‘done’)

}

);

Code snippet is from deferred-pipe-animation.txt

Deferred.pipe allows for more than just convenient chaining; it provides a very handy way to filter Deferreds based on secondary criteria. Deferred.pipe accepts three arguments: a doneFilter, a failFilter, and a progressFilter. The concept of progress is covered in a later section. For now, you focus on filtering standard Deferred objects.

The following code shows an example of filtering an $.ajax response based on component data. This example expands on the skeleton mail update application you saw previously. In this example, assume the mail service always returns a successful HTTP response in the 200 range, even when there’s no mail. Therefore, the $.ajax success function always runs. With Deferred.pipe, you can examine the data provided by the service and test whether or not there are messages in the data object. If there are messages, the response is accepted as a valid, successful response and the data is passed along to the Deferred.done methods. If there are no messages, you reject the promise with Deferred.reject. This sets off the Deferred.fail methods.

$.when(

$.ajax( “/get/mail/” )

) .pipe(

function( data ) {

if ( data.messages.length > 0 )  {

return data

} else {

return $.Deferred().reject();

}

}

).done(

newMessages,

updateMessageList,

updateUnreadIndicator

).fail(

noMessages

).always(

function() {

var date = new Date();

$(“#lastUpdated”).html(“<strong>Folder Updated</strong>: “

+ date.toDateString()

+ ” at “

+ date.toTimeString()

);

}

);

Code snippet is from advanced-pipe.txt

6. Resolving and Rejecting Promises

As you’ve seen in passing throughout this chapter, you occasionally need to manually resolve or reject promises. You’ve already encountered the two most common methods to do this: Deferred .resolve and Deferred.reject. What you haven’t seen is the optional args argument in use. Both of these methods accept an optional argument that will, in turn, be passed to the Deferred.done or Deferred.fail methods depending on the resolution of the promise.

The following example shows a simplified illustration of these optional arguments. Returning to the mail example, the code snippet creates an updated date string to represent the time of the mail update. This string is passed along to the Deferred.fail and Deferred.done methods. Additionally, the number of messages is passed to Deferred.done to update the #message paragraph with the current number of new messages retrieved.

function newMessages( obj ) {

$( “#message” ).text(“you updated at

+ obj.date

+ ” and have “

+ obj.number

+ ” new messages”

)

}

function noMessages( obj ) {

$( “#message” ).text(“you updated at

+ obj.date

+ ” and have no new messages”

)

}

$.when(

$.ajax( “/get/mail/” )

).pipe(

function( data ) {

var date = new Date();

date = date.toDateString() + ” at if ( data.messages.length > 0 ) {

return $.Deferred().resolve({

date: date,

number: data.messages.length });

} else {

return $.Deferred().reject({

date: date

});

}

}

).done(

newMessages

).fail(

noMessages

Code snippet is from resolving-and-rejecting.txt

Additionally, the related methods Deferred.rejectWith() and Deferred.resolveWith() are available to allow you to set a specified this context as the first argument when resolving or rejecting a Deferred.

7. Tracking Progress with Deferred.progress and Deferred.notify

Added in jQuery 1.7, the Deferred.progress and Deferred.notify methods combine to allow for in-process updating of long-running functions.

The basic setup is simple. At different points in your application, you set notify methods that are designed to fire at specific points in an application or in a long-running method. Deferred.notify accepts an optional argument, which will be passed along to the Deferred.progress callback. The related function Deferred.notifyWith accepts a context argument indicating the desired this context for the Deferred.progress callback. The Deferred.progress callback method is added to the chain. This callback method should be designed to handle whatever message was sent along via Deferred.notify or Deferred.notifyWith.

In the simple example that follows, the function longRunning is a Deferred that uses setTimeout to take 5 000ms to resolve its promise. Before the timer is started, there’s a string notification sent out. The progress function accepts that argument and inserts the update text into the DOM. After the process is finished, you notify again that the process is complete and then manually resolve the promise.

In theory, the text update could have been handled by the Deferred.done callback, leaving the progress callback to handle just the original notification. There’s just a nice symmetry to using Deferred.notify at both ends of the process. Also, there’s always a case to be made for keeping repetition of code down to a minimum. So it just makes sense to keep all of the notifications bound to progress.

var longRunning = function() {

return $.Deferred(function(dfd) {

dfd.notify( “operation started” )

var callback = function() {

dfd.notify( “operation finished” );

dfd.resolve();

}

setTimeout( callback, 5000 );

}).promise();

}

longRunning().progress (

function( notification ) {

$( “#notifier” ).text( notification ).fadeIn( 500 );

}).done(function() {

$( “#notifier” ).css({

“color” : “green”,

“font-weight” : “bold”

})

});

Code snippet is from deferred-progress.txt

8. Debugging with Deferred.state

If you ever find the need to debug Deferred resolution (and you will if you ever get deep into using Deferreds), you’ll be taking advantage of Deferred.state.

A simple utility function, Deferred.state returns a string value representing the current state of a Deferred. The three return values are “pending,” “resolved,” and “rejected.”

Table 13-1 defines the three states.

The following example shows Deferred.state calls sprinkled throughout a short Deferred chain based on the long-running function example from the previous section.

var longRunning = function() {

return $.Deferred(function( dfd ) {

dfd.notify( “operation started” )

console.log( dfd.state );

var callback = function() {

dfd.notify( “operation finished” );

dfd.resolve();

}

setTimeout( callback, 5000 );

}).promise();

}

longRunning().progress(

function( notification ) {

console.log( dfd.state );

$( “#notifier” ).text( notification ).fadeIn(500);

}).done(function() {

console.log( dfd.state );

$( “#notifier” ).css({

“color” : “green”,

“font-weight” : “bold”

})

});

Code snippet is from deferred-state.txt

Source: Otero Cesar, Rob Larsen (2012), Professional jQuery, John Wiley & Sons, Inc

Leave a Reply

Your email address will not be published. Required fields are marked *