Way back when I brought up the topic of promises (particularly, jQuery Deferred), and I promised we would come back to the topic someday.
Well, that promise has finally resolved, and this is the done block. Don’t get it? Don’t worry, all shall be explained. If you do get it, and wish there were a done block in the Promise spec… well, read on.
Native Promises
Promises are pretty great.
A lot of time and effort has been expended on explaining why – which would suggest that the answer might not be immediately obvious to everyone, especially if you haven’t spent too much time with pyramid code languishing in callback hell.
For the curious, I’ve included some further reading at the end of this post that should help. For the rest, suffice to say that there are no less than 37 different javascript promise implementations – and these are just the ones that conform to the Promises/A+ spec. Obviously, a lot of people are excited about promises.
Most exciting for those interested in promises in js, however, is the fact that they’ll soon be available natively in a browser near you, if indeed they’re not already. The native implementations will be compliant to the spec, faster (as a result of being native code), and available without the inclusion of an additional library. Yay!
However, if you head to the list of promise implementations linked above and scan through them, including those listed as part of frameworks, you’ll notice that jQuery’s Deferred isn’t among them.
You see, while jQuery’s Deferred plays nicely internally, it isn’t compliant with the Promises/A+ spec that all the cool kids are using. If we’re including jQuery in our project, we could just keep on using Deferred – but what if we really like jQuery’s Deferred API, but want to take advantage of native promises?
Sounds like a problem. Let’s solve it, shall we? I’ve written a little wrapper lib named Defer (original, I know), that let’s you use native promises with an API very similar (although not identical!) to the jQuery Deferred API. Details below.
Head to the github repo for more details, and to see the code.
Defer
Native Promise API Wrapper
Defer is for those who like the jQuery API for Deferred, but want to take advantage of the new, native Promise API built into modern versions of Firefox, Chrome, Opera and Safari (but not ie – see caniuse for up-to-date details).
Defer is also for those who’d like a ‘done’ function (that is, one which executes after the entire promise chain is completed, which doesn’t currently exist in the Promise specification), for those who like to call resolve or reject from outside the Promise constructor, and for those who consider one function per composition ideal, and two or more unbearably ugly (with the exception of .then, because reasons). See below for some more details on this.
Defer is in an immediately-invoked closure, so you can pass whatever context you’d like it to live in to it. By default, it attaches itself to the global window.
Usage:
Simple Example:
var defer = Defer();
defer.then(function(value){
// Do something with the value
// Returning an altered value will propagate to succeeding then and done calls
}).done(function(value){
// Called when the promise chain is fully resolved
}).fail(function(error){
// Called when the promise is rejected, with any value passed into reject
// A nice convention is to make that value a new Error()
});
// ... Do your async stuff ...
defer.resolve(value /* some value your async stuff gave you */);
Another Example:
var p2 = Defer(),
p1 = Defer();
p2.then(function (value) {
console.log(value); // 1
return value + 1;
}).done(function(value){
console.log(value); // 3
}).then(function (value) {
console.log(value); // 2
return value + 1;
});
p2.resolve(1);
p1.resolve(2);
Promise.all([p2, p1]).then(function(vals){
console.log(vals); // [3, 2]
});
Ajax Example:
See the ajax example for the ajax wrapper being used below.
ajax({
url:/somewhere/,
type:GET,
dataType:arraybuffer
}).then(function(res){
// alter the result in someway and return it
return res;
}).done(function(res){
// Do something with the result which was altered in the then block.
});
Native Comparison:
See the MDN Promise articlefor the current form of the Promise API.
Constructor
Native
var promise = new Promise(function(resolve, reject){});
Defer
var promise = Defer();
Resolve or Reject
Native
Must be done within the Promise constructor.
Defer
Can be done anywhere the variable in question is in scope, via .resolve()
or .reject()
.
Adding success/failure handlers
Native
promise.then(successHandler, optionalFailureHandler);
promise.catch(failureHandler);
Defer
promise.then(successHandler, optionalFailureHandler);
promise.fail(failureHandler);
promise.done(doneHandler); // Called when the promise chain fully resolves - not present in native Promise
promise.always(alwaysHandler); // Called when the promise chain fully resolves, or is rejected - not present in native Promise
jQuery Comparison:
See the jQuery Deferred documentation for the full Deferred API.
Adding handlers
jQuery allows you to add an array of handlers, or a comma-seperated series of handlers, for most calls. Defer does not, for purely personal, aesthetic-preference reasons. If you want to add multiple handlers, make multiple calls to .done, .then, etc.
When and race
jQuery
jQuery.when(comma, separated, deferreds).done(function(valuesArray){});
Defer
Promise.all([array, of, Defer, Objects]).then(function(valuesArray){});
Promise.race([array, of, Defer, Objects]).then(function(firstDeferToResolveValue){});
Further Reading
- Promises/A+ – The Promises spec.
- Es6 Promise Draft – The draft of the promise spec being implemented by browsers.
- Coming from jQuery to Q – Discusses some of the differences between jQuery Deferred and a Promise/A+ compliant library.
- MDN Promises – MDN reference on native Promises.
- HTML5rocks article – Article on 3s6 promises.
- Javascript Promises in Wicked Detail – Excellent article breaking down the what, if not the why, of promises.