jQuery Deferreds are a convenient way to manange asynchronous responses in Javascript. Recently, I came up against a problem where I had to handle groups of Deferreds (or rather, promises) collectively, and add more promises to those groups later in the process. To improve the readability and flexibility of the code, I wrapped the Deferreds of each group in a new kind of $.Promises object, which I'd like to introduce here.
What are $.Promises?
The $.Promises object is a convenience wrapper around arrays of jQuery Deferreds or promises. It helps to collect Deferreds and add new ones later on, to delay their resolution and pass them to $.when even before all Deferreds of the collection are set up.
In short, a $.Promises collection adds another layer of asynchronicity to Deferreds and provides an easy-to-read API at the same time.
Making Promises is easy - postponing them, too
Here's how Promises work:
var myPromises = new $.Promises();
'new' is optional. We don't have to add Deferreds to the collection at this point, but we can:
var myPromises = $.Promises( dfd1, dfd2 );
This creates an aggregate promise which will resolve or fail according to the Deferreds 'inside' of it.
myPromises.done( ... ).fail( ... );
$.when( myPromises ).done( ... );
We can add more promises or Deferreds to the collection even if the current ones have all resolved. We just need to treat these Promises like new-year resolutions ;-)
// We delay the resolution of our promises and add some more
myPromises.postpone()
.add( dfd3, dfd4 );
// Now we attach a done handler
$.when( myPromises ).done( whatever );
// ... and resolve all promises.
dfd1.resolve( somearg ); dfd2.resolve(); ... etc for all Deferreds
That would resolve the collection if we had not postponed it. But we have, so we can still add more stuff, and $.when will wait for us to finish:
// $.when( myPromises ), called earlier, will respond to these additions
myPromises.add( dfd5 );
...
When all is set up, $.when will act on the updated collection. This can't be done with ordinary arrays of promises. Finally,
myPromises.stopPostponing();
will unblock the resolution or rejection of myPromises.
The API
$.Promises( [deferred, [deferred]] )
Constructor, returns a new Promises object. A list of promises can be passed as arguments (optional). Can be called with or without 'new'.
.add( promise, [promise] )
Adds one or more promises to the collection. Also accepts Deferreds. Returns the Promises object.
.postpone()
Blocks the resolution of the aggregate Promises. Returns the Promises object.
Calling .postpone() is useful
- if you pass Promises to
$.when while you are still adding new promises to the collection, and want them to impact $.when().
- if you attach
.done() and .fail() handlers early, before you have made all your promises.
- if you are in the process of gathering promises while the ones you have already added might resolve at any time. If all of them do, the collection resolves as well - unless you have called
postpone() to keep the collection open for more promises.
.stopPostponing()
Unblocks the resolution of the collected promises if it has been delayed by postpone(). Returns the Promises object.
.ignoreBelated( [yesno] )
Makes the Promise object ignore attempts to add promises, or call postpone(), when it is too late. Normally, these actions throw a PromisesError exception if they happen after the eventual resolution or failure of the Promise.
Can be turned off again by calling .ignoreBelated( false ). Returns the Promises object.
.isUnresolved()
Returns if the Promises object is still unresolved.
Dependencies
The $.Promises extension requires jQuery 1.6.0 or newer.
A caveat
The $.Promises object is not built with performance in mind. Look at the code - you won't see any of the 'low-level' stuff which makes up the jQuery implementation of Deferreds. Rather, $.Promises is built on top of Deferreds. As a result, the code easy to read and maintain, but the implementation is not as efficient as it could be. That's the tradeoff.
You might find it reassuring to know, though, that the $.Promises extension is fully tested.
View the source | Run the tests | Dowload the extension
Your comment is most welcome - and it will be even more if it is somehow related to the topic ;-) However, we probably all agree that spam and similar nonsense ("cooool thx for sharing!!!!!") shouldn't make it online.