$.Deferred
Deferred offer a solution for abstracting non-blocking
elements (such as the response to ajax requests) and achieve this by creating a promise
, which aims to return a response at some point in the future.
At a high-level, deferreds can be thought of as a way to represent asynchronous operations which can take a long time to complete. The Deferred Pattern describes an object which acts as proxy
for some unit of computation that may or may not have completed. They're the asynchronous alternative to blocking functions and the general idea is that rather than your application blocking. While it awaits some request to complete before returning a result, a deferred object can instead be returned immediately. You can then attach callbacks to the deferred object: they will be called once the request has actually completed.
// 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).
function doAsync() {
var deferredObject = $.Deferred();
setTimeout(function() {
deferredObject.resolve();
}, 1000);
// return a promise object
return deferredObject.promise();
}
// Finally, the promise object associated with the deferred object is returned.
// Through the promise object the code calling the doAsync() function can decide what should happen
// if the deferred object's state changes to resolved or failed.
var promise = doAsync();
promise.done(function () {
console.log("Executed after a delay");
});
// Furthermore, since the done() and fail() functions return the promise object itself,
// you could shorten the above code to:
doAsync()
.done(function () {
console.log("Executed after a delay");
}).fail(function () {
console.log("Executed if the async work fails");
});
Deferred objects in jQuery represents a unit of work that will be completed later, typically asynchronously. Deferred objects are typically wherever you would normally pass a call back functions to be executed when some asynchronous unit of work completes.
Deferreds can be added during the entire lifetime of the deferred object. The key to this behavior is callbacks registered with the deferred will run immediately if the deferred is already resolved.
The most common cases when asynchronous work are
- AJAX calls
- Loading resources over network
- Set Timeout or Set Interval
- Animations
- User Interactions\/User behavior
Deferred Object return a Promise object
Ajax
jqXHR
has implemented promise interfaceWhen
resolve()
orreject()
functions are called, any callback functions added via the promise object, or deferred objects will get called.
How to make it work
register callback function by using deferred.done or other methods.
then call the main function,
invoke $.Deferred().resolve()- the callback functions registered will be invoked after
// create a deferred wrapper
$.Deferred(funciton (){
// do something
});
// create a deferred wrapper and return promise
function performLongTask() {
return $.Deferred(function(def) {
// run some task, then resolve it.
// pass value to the callback functions
setTimeout(function() {
performLongTaskSync();
def.resolve(/* passing some value */);
}, 10)
})
}
var observer = performLongTask();
observer.done(function(total) { // data passed from resolve() function
displayMessage("Done Callback Executed: " + total);
});
observer.fail(function(total) {
displayMessage("Fail Callback Executed: " + total);
});
// register succeeded and failed function
$.Deferred().then (succeededFunction, failedFunction);
// register callback function when it is done.
$.Deferred().done(callbackFunction())
// aggregrated deferred object
$.when(org1, org2, org3)
function successFunc(){ console.log( “success!” ); }
function failureFunc(){ console.log( “failure!” ); }
$.when( $.ajax( "/main.php" ),
$.ajax( "/modules.php" ),
$.ajax( “/lists.php” ) )
.then( successFunc, failureFunc );
// progress notificatio
$.Deferred().notify(progressValue);
$.Deferred().progress(function (progressValue){
// do something with the progress value
});
Promises
In most cases, if you're returning a deferred you don't want the consumer of your deferred to be able to resolve or reject it -- you want to manage that yourself. In that case, you return a promise.
Promise object is contained in a deferred object. Via promise object, you can specify what is to happen when the unit of work complete. you do that by setting callback functions on the promise object. Allow you to avoid nasty tricks to synchronize parallel asynchronous functions and the need to nest callbacks inside callbacks inside callbacks…
In JQuery's terminology, a promise is a read-only
deferred. A promise allows you to attach callbacks and ask the deferred’s status, but you can’t tell it to change its state (e.g. resolve, reject). jQuery’s ajax()
method returns a promise, since its up to the internal code handling the AJAX request to decide whether the request succeeded or failed.
This is powerful since you can now treaty promises as first-class objects, passing them around, aggregrating them, and so on, instead of inserting dummy callbacks that tie together other callbacks in order to do the same.
Promise object contains a subset of methods from Deferred with following functions
done()
Handlers to be called when the Deferred object is resolved.
The
deferred.done()
method accepts one or more arguments, all of which can be either a single function or an array of functions.When the
Deferred
is resolved, the doneCallbacks are called. Callbacks are executed in the order they were added. Sincedeferred.done()
returns the deferred object, other methods of the deferred object can be chained to this one, including additional .done() methods. When the Deferred is resolved, doneCallbacks are executed using the arguments provided to theresolve
orresolveWith
method call in the order they were added.fail()
always()
Handlers to be called when the Deferred object is either resolved or rejected.
progress()
- used to attach a progress callback function to the promise object
- this callback function is called whenever the notify() function is called
then()
Handlers to be called when the Deferred object is resolved, rejected, or still in progress.
return a new promise that can filter the status and values of the deferred function
state()
- return the state of the deferred object connected to the promise.
- pending\/resolved\/rejected
when()
- Synchronizing asynchronous events
var name = $.post('/echo/json/', {json:JSON.stringify({'name':"Matt Baker"})}); var lastUpdate = $.post('/echo/json/', {json:JSON.stringify({'lastUpdate':"Hello World"})}); $.when(name, lastUpdate) .done(function (nameResponse, lastUpdateResponse) { var name = nameResponse[0].name; var lastUpdate = lastUpdateResponse[0].lastUpdate; $("#render-me").html(name+"'s last update was: "+lastUpdate); }) .fail(function () { $("#error").html("an error occured").show(); }); // Why the arrays in the done handler? // The new done callbacks receive all the data passed to the done callback of each deferred // that's being "combined" with $.when. In this case, we get two arrays - // one with all the data that would have been sent to "done" callback of the name request, // and one for the data returned to the lastUpdate request.
Why use Deferred/Promise Object?
One of the benefits that arise from using deferred object is that we can partition our code up into small functions that handle specific activities. To allow further decomposition of our code, deferred objects provide support for registering multiple callbacks for the same outcome
The “Deferred” pattern describes an object which acts as a proxy for some unit of computation that may or may not have completed. The pattern can apply to any asynchronous process: AJAX requests, animations, or web workers to name a few. Even user behavior can be thought of as a “delayed computation.”
All of jQuery's AJAX methods now return an object containing a "promise", which is used to track the asynchronous request. The promise is a read-only view into the result of the task. Deferreds look for the presence of a promise() method to determine whether an object is observable or not.
But why not return the whole object? If this were the case, it would be possible to muck with the works, maybe pragmatically "resolve" the deferred, causing all bound callbacks to fire before the AJAX request had a chance to complete. Therefore, to avoid potentially breaking the whole paradigm, only return the dfd.promise().
The Deferred Object vs. Promise Object
- A deferred object also has a
done()
andfail()
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.- 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()
).
// pseudo-code with promise
promise = callToAPI( arg1, arg2, ...);
promise.then(function( futureValue ) {
/* handle futureValue */
});
promise.then(function( futureValue ) {
/* do something else */
});
// pseudo-code with resolved and rejected
promise.then( function( futureValue ) {
/* we got a value */
} , function() {
/* something went wrong */
} );
// several results
when(
promise1,
promise2,
...
).then(function( futureValue1, futureValue2, ... ) {
/* all promises have completed and are resolved */
});
// jQuery Deferred
var deferred = $.Deferred();
always(callbacks[, callbacks, ..., callbacks]):
- Add handlers to be called when the Deferred object is either resolved or rejected.
done(callbacks[, callbacks, ..., callbacks]):
- Add handlers to be called when the Deferred object is resolved.
fail(callbacks[, callbacks, ..., callbacks]):
- Add handlers to be called when the Deferred object is rejected.
notify([argument, ..., argument]):
- Call the progressCallbacks on a Deferred object with the given arguments.
notifyWith(context[, argument, ..., argument]):
- Call the progressCallbacks on a Deferred object with the given context and arguments.
progress(callbacks[, callbacks, ..., callbacks]):
- Add handlers to be called when the Deferred object generates progress notifications.
promise([target]):
- Return a Deferred‘s Promise object.
reject([argument, ..., argument]):
- Reject a Deferred object and call any failCallbacks with the given arguments.
rejectWith(context[, argument, ..., argument]):
- Reject a Deferred object and call any failCallbacks with the given context and arguments.
resolve([argument, ..., argument]):
- Resolve a Deferred object and call any successful doneCallbacks with the given arguments.
resolveWith(context[, argument, ..., argument]):
- Resolve a Deferred object and call any successful doneCallbacks with the given context and arguments.
state(): Determine the current state of a Deferred object.
then(resolvedCallback[, rejectedCallback[, progressCallback]]):
- Add handlers to be called when the Deferred object is resolved, rejected, or still in progress.
$.when()
- return Promise(s)
Sample Code Snippet - Ajax
// $.ajax method in jQuery return jqXHR which implement Promise interface
var post = $.ajax({
url: "/echo/json",
data: {
json: JSON.stringify({
firstName : "Jose",
lastName: "Romaniello"
})
},
type: "POST"
});
post.done(function (p){
alert(p.firstName + " save. ");
});
post.fail(function (){
alert("error!");
});
// you can add as many callbacks as you want.
// the syntax is clear
Sample Code Snippet - Use Deferred Object as Cache
var cachedScriptPromises = {};
$.cachedGetScript = function(url, callback) {
// it handle both complete and inbound requests transparently.
if (!cachedScriptPromises[url]) {
// if there is no promise for a given Url
// create a deferred object, wrapping a external call
// return a Promise
cachedScriptPromises[url] = $.Deferred(function(defer) {
$.getScript(url)
.then(defer.resolve, defer.reject);
}).promise();
}
// attach the callback to promise
return cachedScriptPromises[url].done(callback);
};
/*
Refactor to a Generic asynchronous cache
*/
$.createCache = function(requestFunction) {
var cache = {};
return function(key, callback) {
if (!cache[key]) {
// $.Deferred[beforeStart]
// A function is called just before the constructor return.
// The function accepts a Deferred object used as the context (this) of the function
cache[key] = $.Deferred(function(defer) {
// function with following signature
requestFunction(defer, key);
}).promise();
}
return cache[key].done(callback);
};
};
$.cachedGetScript = $.createCache(function(defer, url) {
$.getScript(url).then(defer.resolve, defer.reject);
});
$.cachedGetScript(url).then(successCallback, errorCallback);
// caching image loading
$.loadImage = $.createCache(function(defer, url) {
var image = new Image();
function cleanUp() {
image.onload = image.onerror = null;
}
defer.then(cleanUp, cleanUp);
image.onload = function() {
defer.resolve(url);
};
image.onerror = defer.reject;
image.src = url;
});
$.loadImage("my-image.png").done(callback1);
$.loadImage("my-image.png").done(callback2);
// Caching Data api calls (response)
$.searchTwitter = $.createCache(function(defer, query) {
$.ajax({
url: "http://search.twitter.com/search.json",
data: {
q: query
},
dataType: "jsonp",
success: defer.resolve,
error: defer.reject
});
});
$.searchTwitter("jQuery Deferred", callback1);
$.searchTwitter("jQuery Deferred", callback2);
// timer example
var readyTime;
$(function() {
readyTime = jQuery.now();
});
$.afterDOMReady = $.createCache(function(defer, delay) {
delay = delay || 0;
$(function() {
var delta = $.now() - readyTime;
if (delta >= delay) {
defer.resolve();
} else {
setTimeout(defer.resolve, delay - delta);
}
});
});
// caching Ajax Request Result
var cache = {};
function getData( val ){
// return either the cached value or jqXHR object wrapped Promise
return $.when( cache[ val ] ||
$.ajax('/foo/', {
data: { value: val },
dataType: 'json',
success: function( resp ){
cache[ val ] = resp;
}
}) );
}
getData('foo').then(function(resp){
// do something with the response, which may // or may not have been retreived using an
// XHR request.
});
fetch_sources = function (schema_urls) {
var fetch_one = function (url) {
return $.ajax({
url: url,
data: {},
contentType: "application/json; charset=utf-8",
dataType: "json",
});
}
return $.map(schema_urls, fetch_one);
}
var promises = fetch_sources(data['schemas']);
// In your case, you have an array of promises; you don't know how many parameters you're passing to $.when.
// Passing the array itself to $.when wouldn't work, because it expects its parameters to be promises, not an array.
// That's where .apply comes in. It takes the array, and calls $.when with each element as a parameter
// (and makes sure the this is set to jQuery/$), so then it all works :-)
// Note: You'll want to pass $ instead of null so that this inside $.when refers to jQuery.
// It shouldn't matter to the source but it's better then passing null.
$.when.apply(null, promises).then(
function () {
var schemas = $.map(arguments, function (a) {
return a[0]
});
start_application(schemas);
}, function () {
console.log("FAIL", this, arguments);
});
Sample Code Snippet - Synchronizing multiple animations
$.fn.animatePromise = function( prop, speed, easing, callback ) {
var elements = this;
return $.Deferred(function( defer ) {
elements.animate( prop, speed, easing, function() {
defer.resolve();
if ( callback ) {
callback.apply( this, arguments );
}
});
}).promise();
};
// synchronize between different animations using jQuery.when
var fadeDiv1Out = $( "#div1" ).animatePromise({
opacity: 0
}),
fadeDiv2In = $( "#div1" ).animatePromise({
opacity: 1
}, "fast" );
$.when(
fadeDiv1Out,
fadeDiv2In
).done(function() {
/* both animations ended */
});
// Help method
$.each([
"slideDown",
"slideUp",
"slideToggle",
"fadeIn",
"fadeOut",
"fadeToggle"
], function( _, name ) {
$.fn[ name + "Promise" ] = function( speed, easing, callback ) {
var elements = this;
return $.Deferred(function( defer ) {
elements[ name ]( speed, easing, function() {
defer.resolve();
if ( callback ) {
callback.apply( this, arguments );
}
});
}).promise();
};
});
// synchronize between animations using the new helpers as follows:
$.when(
$( "#div1" ).fadeOutPromise(),
$( "#div2" ).fadeInPromise( "fast" )
).done(function() {
/* both animations are done */
});
Sample Code Snippet - Click Once
/*
The code works as follows:
check if the element already has a deferred attached for the given event
if not, create it and make it so it is resolved when the event is fired the first time around
then attach the given callback to the deferred and return the promise
*/
$.fn.bindOnce = function( event, callback ) {
var element = $( this[ 0 ] ),
defer = element.data( "bind_once_defer_" + event );
if ( !defer ) {
defer = $.Deferred();
function deferCallback() {
element.unbind( event, deferCallback );
defer.resolveWith( this, arguments );
}
element.bind( event, deferCallback )
element.data( "bind_once_defer_" + event , defer );
}
return defer.done( callback ).promise();
};
//
$.fn.firstClick = function( callback ) {
return this.bindOnce( "click", callback );
};
// after refactoring
var openPanel = $( "#myButton" ).firstClick();
openPanel.done( initializeData );
openPanel.done( showPanel );
function showDiv() {
var dfd = $.Deferred();
$('#foo').fadeIn( 1000, dfd.resolve );
return dfd.promise();
}
// same as:
function showDiv() {
return $.Deferred(function( dfd ) {
$('#foo').fadeIn( 1000, dfd.resolve );
}).promise();
}
function wait(ms){
var deferred = $.Deferred();
setTimeout(function () {deferred.resolve()}, ms);
return deferred.promise();
}
wait(150).then(function () {
// something will be executed.
});
Sample Code Snippet - Contact Form
/*
The saveContact() function first validates the form and saves the result into the variable valid.
If validation fails, the deferred is resolved with an object containing a success boolean
and an array of error messages. If the form passes validation,
the deferred is resolved, except this time the success handler receives
the response from the AJAX request.
The fail() handler responds to 404, 500,
and other HTTP errors that could prevent the AJAX request from succeeding.
*/
function saveContact( row ) {
var form = $.tmpl(templates["contact-form"]),
valid = true,
messages = [],
dfd = $.Deferred();
/*
bunch of client-side validation here
*/
if( !valid ) {
dfd.resolve({
success: false,
errors: messages
});
} else {
form.ajaxSubmit({
dataType: "json",
success: dfd.resolve,
error: dfd.reject
});
}
return dfd.promise();
};
saveContact( row ).done(function(response) {
if( response.success ) {
// saving worked; rejoice
} else {
// client-side validation failed
// output the contents of response.errors
}
}).fail(function(err) {
// AJAX request failed
});
Reference
- Oreilly JQuery Deferred Source Code
-
A set of three utility methods for use with Jquery's Deferred object
- Respeto - a deferred image loader
- JQuery Deferred Labs
- JQuery Futures
- JQuery Defer
- An Example to demostrate use of JQuery Deferred Object
- JQuery Plugin Deferred When Extension
- JSON Promise
- JQuery Deferred Reporter
- JQuery Deferred Sequence
- JQuery Timeout
- JQuery Postpone
MSDN - Creating Responsive Applications Using JQuery Deferred and Promises
Written by Addy Osmani, discussed the basic concept of deferred object and promise. Also provided very good examples of how to apply deferred object in real time application
Stackoverflow: How can Jquery deferred be used
Various implementation of jQuery deferred object, expecially the caching.
- Deferreds Coming to JQuery - Rebacca Murphey
- You are missing the point of Promise
-
No finished yet, some of the example at the end are not understandable for now.
jQuery.Deferred is the most important client-side tool you have
An introduction to JQuery Deferred/Promise and the design pattern in general
- Providing Synchronous/Asynchronous Flexibility With JQuery.When
- Telerik Blog - What's the point of Promises?
- Write Better JavaScript With Promise