Remember when you wanted to associate an event handler to a mouseclick, you would assign it to the element’s onclick event, as in mywidget.onclick = myhandler;. This became problematic when another method also wanted to get in on the click action since you could only assign one function at a time. Eventually, the issue was solved by the addEventListener() DOM function, which allowed you to add as many listeners as you wanted. Fast forward to the present, and a similar problem has emerged with Ajax calls. This time it’s Ajax’s limitation of only supporting one callback function. jQuery introduced the Deferred object in version 1.5 to solve this problem. It can register multiple callbacks into callback queues, invoke callback queues, and relay the success or failure state of any synchronous or asynchronous function. In this article, we’ll learn how to use the jQuery Deferred object with Promises.
What’s In a Promise?
Prior to jQuery 1.5, a typical Ajax call looked like this:
[javascript]$.ajax({
url: "/ServerResource.txt",
success: successFunction,
error: errorFunction
});[/javascript]
Since then, Ajax calls’ return object (jQuery XMLHttpRequest (jqXHR)) implements theCommonJS Promises/A interface, which offers greater flexibility and, hopefully, consistency across libraries.
url: "/ServerResource.txt"
});
promise.done(successFunction);
promise.fail(errorFunction);
promise.always(alwaysFunction);[/javascript]
All three done(), fail(), and always() handlers return the same jQuery XMLHttpRequest (jqXHR) object, so it is perfectly acceptable to chain them together:
[javascript]$.ajax( "example.php" )
.done(function() { alert("success"); })
.fail(function() { alert("error"); })
.always(function() { alert("complete"); });[/javascript]
However, you will have to store the jqXHR object to a variable if you need to refer to it further down the line:
[javascript]var jqxhr = $.ajax( "example.php" )
.done(function() { alert("success"); })
.fail(function() { alert("error"); })
.always(function() { alert("complete"); });
// perform some work here …
// Set another completion function for the request above
jqxhr.always(function() { alert("another complete"); });[/javascript]
Another way to combine the handlers is to use the then() method of the Promise interface. It accepts all three handlers as arguments. With regards to jQuery, prior to version 1.8, you could pass an array of functions to the then() method:
[javascript]$.ajax({url: "/ServerResource.txt"}).then([successFunction1, successFunction2, successFunction3],
[errorFunction1, errorFunction2]);
//same as
var jqxhr = $.ajax({
url: "/ServerResource.txt"
});
jqxhr.done(successFunction1);
jqxhr.done(successFunction2);
jqxhr.done(successFunction3);
jqxhr.fail(errorFunction1);
jqxhr.fail(errorFunction2);[/javascript]
Since 1.8, the then() method returns a new promise that can filter the status and values of a deferred through a function, replacing the now-deprecated deferred.pipe() method. For all signatures, the arguments can be set to null if you don’t want to assign a handler for that event type. Best of all, you can rest assured that callbacks are guaranteed to run in the order they were passed in.
[javascript]var promise = $.ajax({
url: "/ServerResource.txt"
});
promise.then(successFunction, errorFunction);[/javascript]
[javascript]var promise = $.ajax({
url: "/ServerResource.txt"
});
promise.then(successFunction); //no handler for the fail() event[/javascript]
Chaining then() Functions
Then functions can be chained together to call multiple functions in succession:
[javascript]var promise = $.ajax("/myServerScript1");
function getStuff() {
return $.ajax("/myServerScript2");
}
promise.then(getStuff).then(function(myServerScript2Data){
// Do something with myServerScript2Data
});[/javascript]
Combining Promises
The Promise $.when() method is the equivalent of the logical AND operator. Given a list of Promises, when() returns a new Promise whereby:
- When all of the given Promises are resolved, the new Promise is resolved.
- When any of the given Promises is rejected, the new Promise is rejected.
The following code uses the when() method to make two simultaneous Ajax calls and execute a function when both have successfully finished:
[javascript]var jqxhr1 = $.ajax("/ServerResource1.txt");
var jqxhr2 = $.ajax("/ServerResource2.txt");
$.when(jqxhr1, jqxhr2).done(function(jqxhr1, jqxhr2) {
// Handle both XHR objects
alert("all complete");
});[/javascript]
Promise States
At any moment in time, promises can be in one of three states: unfulfilled, resolved or rejected. The default state of a promise is unresolved. Any handlers set are queued to be executed later. For instance, if an Ajax call is successful then $.resolved is called, its state is set to resolved and any “done” handlers set are executed. Should the call fail, $.rejected is called, its state is set to rejected and any “fail” handlers set are executed.
Making Your Own Deferred Process
By creating a new Deferred object via the jQuery.Deferred() method, we can setup our own deferred processes. In the following example, a <DIV> or <SPAN> element is updated based on the state of our process. The setInterval() method starts the process in motion, while setTimeout() determines when the process should end. Inside the process() method, a Deferred object is assigned to a local variable so that we can call its resolve(), promise(), and notify() events:
[javascript]var timer;
$(‘#result’).html(‘waiting…’);
var promise = process();
promise.done(function() {
$(‘#result’).html(‘done.’);
});
promise.progress(function() {
$(‘#result’).html($(‘#result’).html() + ‘.’);
});
function process() {
var deferred = $.Deferred();
timer = setInterval(function() {
deferred.notify();
}, 1000);
setTimeout(function() {
clearInterval(timer);
deferred.resolve();
}, 10000);
return deferred.promise();
}[/javascript]
It can also be written using the then() method:
[javascript]var timer;
(function process() {
$(‘#result’).html(‘waiting…’);
var deferred = $.Deferred();
timer = setInterval(function() {
deferred.notify();
}, 1000);
setTimeout(function() {
clearInterval(timer);
deferred.resolve();
}, 10000);
return deferred.promise();
})().then(function() { $(‘#result’).html(‘done.’); },
null,
function() { $(‘#result’).html($(‘#result’).html() + ‘.’); });[/javascript]
Conclusion
Promises are definitely the new way to go when handling asynchronous processes. Forget about traditional callbacks and give the Promises/A API a try. It’s a big step in the right direction.
The talk about this can be found in the following video
Resources:
- http://www.htmlgoodies.com/beyond/javascript/making-promises-with-jquery-deferred.html
- http://danieldemmel.me/blog/2013/03/22/an-introduction-to-jquery-deferred-slash-promise/