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.
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.
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.
$.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
$.when
while you are still adding new promises to the collection, and want them to impact $.when()
..done()
and .fail()
handlers early, before you have made all your promises.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.
The $.Promises
extension requires jQuery 1.6.0 or newer.
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 trade-off.
You might find it reassuring to know, though, that the $.Promises
extension is fully tested.
View the source | Run the tests | Dowload the extension
Update 25 July 2014: Head over to Github for the current version, or install the jquery.promises
package with Bower or npm.