- Authors
Update 2 (2015/10/23)
I realised I should really put an update on this post, at this point you should really not use Deferred and instead of jQuery (or any other 3rd party library) just start relying on the native ES6 / ES2015 Promise – unless you need some advanced functionality, in which case try something like Axios.
For a more up to date introduction to Promises, check out this post on the PouchDB blog.
I gave an introductory talk a while back at the London Ajax User Group about jQuery Promises after which there was a lively debate, so I thought it would be great to post the content of the slides with some notes as a sort of tutorial.
Update (2012/09/01)
As it turns out there was a nice video recorded of the talk, if you have 10 minutes free give it a watch!
If the embedded video above doesn't load, open it here: https://vimeo.com/73539595
Introduction to jQuery Promises / Deferreds - London Ajax User Group 12.02.2013
The original presentation is on Github (made with Keydown as presentation engine which doesn't seem to handle resizing well enough to be embeddable): https://www.danieldemmel.me/jquery-deferred-intro/jquery-deferred-intro/slides.html
A simple CORS AJAX example
As a simple scenario to optimise I set up a not very useful example of loading 2 articles from freeCodeCamp and showing the first sections of each.
So in which cases are Promises useful?
AJAX request handler spaghetti
$.ajax({
type: "GET",
url: "https://www.freecodecamp.org/news/access-control-allow-origin-header-explained/",
success: function(response) {
var insertDiv1 = $("<div></div>");
insertDiv1.html(
$(response)
.find("h1")
.html()
);
$.ajax({
type: "GET",
url: "https://www.freecodecamp.org/news/create-a-custom-fetch-api-from-xmlhttprequest-2cad1b84f07c/",
success: function(response) {
var insertDiv2 = $("<div></div>");
insertDiv2.html(
$(response)
.find("h1")
.html()
);
$("body").append(insertDiv1, "<hr/>", insertDiv2);
}
});
}
});
Timing issues (DOM ready, animations, etc)
$(document).ready(function() {
$.ajax({
type: "GET",
url: "https://www.freecodecamp.org/news/access-control-allow-origin-header-explained/",
success: function(response) {
var insertDiv1 = $("<div></div>");
insertDiv1.html(
$(response)
.find("section")
.html()
);
$.ajax({
type: "GET",
url: "https://www.freecodecamp.org/news/create-a-custom-fetch-api-from-xmlhttprequest-2cad1b84f07c/",
success: function(response) {
var insertDiv2 = $("<div></div>");
insertDiv2.html(
$(response)
.find("section")
.html()
);
$("body").append(insertDiv1, "<hr/>", insertDiv2);
}
});
}
});
});
But what are these Deferreds and Promises really?
Deferred
- A proxy for an asynchronous, future event
- Has an interface for getting
resolve()
d orreject()
ed - Starts in
pending
state, can only be finished once - Calls listeners immediately (but always async) once resolved
Promise
- Allows listening and state inspection (using
state()
), but completely immutable so no interface for resolution - Basic (jQuery specific) listeners are
done()
andfail()
- Can be chained with
then()
(used to bepipe()
) - Can be grouped and processed using
$.when()
How do they work?
A canonical Deferred example
Setting up a listener and triggering it with resolve:
var deferred = $.Deferred();
deferred.done(function(value) {
alert(value);
});
deferred.resolve("hello world");
This also works as it doesn't matter if a Deferred is already resolved it will still trigger the callback we attach to it:
var deferred = $.Deferred();
deferred.resolve("hello world");
deferred.done(function(value) {
alert(value);
});
A canonical Promise + When example
Return a Promise from a method and attach a listener to it (can have more than one):
function getPromise() {
var deferred = $.Deferred();
setTimeout(function() {
deferred.resolve("hurray");
}, 1000);
return deferred.promise();
}
$.when(getPromise()).done(function(value) {
alert(value);
});
So let's untangle our example
Create a Promise for DOM ready and the two AJAX requests and wait for all of them to be fulfilled:
function getReady() {
var deferredReady = $.Deferred();
$(document).ready(function() {
deferredReady.resolve();
});
return deferredReady.promise();
}
var firstRequest = $.ajax({
url: "https://www.freecodecamp.org/news/access-control-allow-origin-header-explained/"
}),
secondRequest = $.ajax({
url: "https://www.freecodecamp.org/news/create-a-custom-fetch-api-from-xmlhttprequest-2cad1b84f07c/"
});
$.when(getReady(), firstRequest, secondRequest).done(function(
readyResponse,
firstResponse,
secondResponse
) {
var insertDiv1 = $("<div></div>");
insertDiv1.html(
$(firstResponse[0])
.find("section")
.html()
);
var insertDiv2 = $("<div></div>");
insertDiv2.html(
$(secondResponse[0])
.find("section")
.html()
);
$("body").append(insertDiv1, "<hr/>", insertDiv2);
});
Another solution
The AJAX request can be already fired off while we wait for the DOM (also showing how can we chain listeners with then()
):
function getReady() {
var deferredReady = $.Deferred();
$(document).ready(function() {
deferredReady.resolve();
});
return deferredReady.promise();
}
var firstRequest = $.ajax({
url: "https://www.freecodecamp.org/news/access-control-allow-origin-header-explained/"
}),
secondRequest = $.ajax({
url: "https://www.freecodecamp.org/news/create-a-custom-fetch-api-from-xmlhttprequest-2cad1b84f07c/"
}),
firstDiv,
secondDiv;
$.when(firstRequest, secondRequest)
.then(function(firstResponse, secondResponse) {
firstDiv = $("<div></div>").html(
$(firstResponse[0])
.find("section")
.html()
);
secondDiv = $("<div></div>").html(
$(secondResponse[0])
.find("section")
.html()
);
return getReady();
})
.done(function(readyResponse, firstResponse, secondResponse) {
$("body").append(firstDiv, "<hr/>", secondDiv);
});
Dealing with rejection
When a Promise gets reject()
ed it will immediately cascade down the then()
chain so you only need to handle it at the end (with jQuery it's only since 1.8+ though).
getTweetsFor("domenic") // promise-returning function
.then(function(tweets) {
var shortUrls = parseTweetsForUrls(tweets);
var mostRecentShortUrl = shortUrls[0];
return expandUrlUsingTwitterApi(mostRecentShortUrl); // promise-returning function
})
.then(httpGet) // promise-returning function
.then(
function(responseBody) {
console.log("Most recent link text:", responseBody);
},
function(error) {
console.error("Error with the twitterverse:", error);
}
);
I've taken this example from Domenic Denicola's blog post "You're Missing the Point of Promises" which is a great next step on the path of understanding Promises and asynchronous control flows. Go and read it now!
A few more pointers
- In case there's a long request you can send updates to a
progress()
listener usingnotify()
- You can insert transformations into the
then()
chain
$.when({ testing: 123 }).done(
function(x) {
alert(x.testing);
} /* alerts "123" */
);
- And finally it must be said that jQuery Deferred is by far not the only one, see Promises/A spec and the clarified Promises/A+ spec. If you're not already using jQuery then Q, rsvp.js or when might be better alternatives with seamless interoperability due to stricter adherence to the CommonJS specs.