Using jQuery Deferred to wait for multiple Backbone models to save
Backbone's Model implementation is great for most things, but one thing I've
had a hard time with is waiting for multiple models to save before proceeding.
Backbone offers a success
callback like this:
model.save
success: ->
alert("We did it!")
You could also use the sync
callback like this:
model.on 'sync', ->
alert("We did it!")
model.save()
But what about when you want to wait for multiple models to finish saving, all with their own asynchronous requests?
Don't Nest It. Chain It!
The jQuery Deferred object is a chainable utility object that can register
multiple callbacks to relay the success or failure state of an asynchronous
operation. Lucky for us, Backbone's model.save()
method returns a jqXHR
object, which implements the Deferred API. This means that instead of writing
this:
model.save
success: ->
alert("We did it!")
We can write this:
model.save().done(-> alert("We did it!"))
That's a nice bit of syntactic sugar, but it still doesn't address our original problem: How can we wait for multiple models to save, and then fire the callback to alert the user?
Tell Me When You're All Done
jQuery.when
allows us to combine multiple Deferred objects into one
aggregate Deferred object, such that we can chain callbacks to be executed
only when all the objects have resolved.
For sake of example, let's say we have a collection of 3 Backbone models we'd like to save:
collection = new MyCollection([{name: "Steve"}, {name: "Dave"}, {name: "Tom"}])
Remember that Backbone's model.save()
returns a jqXHR object, which acts as
a Deferred. So we can run:
xhrs = collection.map (model) -> model.save()
This will create an array xhrs
containing the jqXHR objects for each
individual save operation. To alert the user when all of them complete, we can
use jQuery.when
:
jQuery.when(xhrs...).done(-> alert("All of them are saved!"))
Note: The splat (...
) syntax above is required to split the xhrs
array
into separate arguments. This had me stumped---without the splat, jQuery
treats the array as a single Deferred object, which obviously doesn't execute
the callbacks in the same manner as multiple jqXHR objects.
And Tell Me When One of You Failed
We can also use Deferred's fail()
method to alert the user that one or more
of the save operations failed:
jQuery.when(xhrs...).
done(-> alert("We succeeded!")).
fail(-> alert("We failed."))
Conclusion
The jQuery Deferred API is a powerful way to elegantly wait for the completion
of asynchronous operations in your Backbone application. While it's tempting to
resort to workarounds like using setTimeout
to wait an arbitrary amount of
time for operations to complete, using jQuery.when
means you don't introduce
race conditions into your application.
If you have any questions or if something isn't working as described above, please leave me a comment. I'll try my best to answer as soon as I can.