Leveraging Deferreds in Backbonejs
TL;DR - Assigning Deferreds as Model / Collection properties can make
your life much easier.
UPDATE: We're holding a full day Intro to Backbone.js workshop on June 16th, 2012. Details here.
We've been working with Backbone.js a lot this year and this is one of
the best tricks we've discovered for making complex Backbone apps a bit
easier to manage. Most Backbone apps require some sort of bootstrapping
process to initialize themselves with data. In a perfect world, you can
populate your application with JSON by direct reference to your backend
code, as illustrated by this example from the Backbone Docs:
But for the majority of implementations, this just isn't practical to do.
Either you have too much data to load it synchronously or you may not
even have a conventional view layer connected to your backend. In any
case, we've found a clever solution to getting data and rendering in a
Deferred Objects have been part of jQuery since the 1.5 release and are
an underlying part of all AJAX functions in jQuery. If you're looking
for a more detailed explaination of how Deferreds work, check out
Nico's post on the subject. For now, let's settle for the pithy
explaination from the jQuery API Docs:
"[Deferred objects are] chainable utility object[s] that can register
multiple callbacks into callback queues, invoke callback queues, and
relay the success or failure state of any synchronous or asynchronous
Basically, if you have access to a deferred object, you can reliably
bind callback functions to the state of a function now and forever,
regardless of how or when that function is "resolved."
Now what's really cool is that jQuery AJAX functions return a Deferred
object. This is what lets you chain callbacks directly off of
AJAX methods, but it's even better when you can leverage that returned
value from a completely different scope. This becomes a really important
behavior in a Backbone app, but only if you know how to take advantage
Using Deferreds with Backbone
First, let's take a look at a simple Backbone example:
Press the "run" button on the upper right corner to see it's output.
Put them in your Collections & Models
Let's start off with a Backbone Collection (or Model) that we want to
automatically populate itself with data on creation. All we need to do is make a
this.fetch() in the initializer, right? Absolutely. But the difference
is that we assign the Deferred object returned from
Access them in your Views
Now there's no reason why you'd need to do this in the initialize
method, but it makes grabbing a Collection's data as simple as creating
an instance of it. This probably doesn't look much different than what
you're already doing with Backbone. But here's the fun part:
There are a few things at play in this example: we're defining a basic
view, and instantiating it along with the collection from above. When we
create the view instance, our collection is passed into it, linking the
two together in our app. This allows us to reference the collection from
within our view.
Solve state problems by ignoring them
Notice how we're able to call myView.render() as soon as we've
created the view instance? This is because there's a deferred
object between us and rendering the view--and we can rely on that to execute
this as soon as it's ready. When render creates a callback off of the
collection's deferred object, we know that this method won't be called
until the collection.fetch() is complete.
There's no need for us to check anything other than the deferred object
to know whether the request has succeeded or failed.
By assigning the deferreds issued by Model and Collection methods which
return AJAX calls, it becomes really easy to code Backbone Apps that load
data asynchronously, without having to write any extra code to check if
your data is loaded.
And $.when() you've got more than 1...
For extra credit, you can use jQuery.when() to handle multiple
deferred objects simultaneously and when you need to deal with
objects that might be deferreds. Here's a quick example:
When I'm not replying to Backbone issues on Github, you can usually find me in #documentcloud on Freenode IRC, nick wookiehangover. Please hit me up with any questions!
Update [11/25/11]: While playing around with different ways of using Common JS Promises outside of jQuery, I implemented jQuery style deferreds as an Underscore module. The project is available on Github if you're interested!
Update [12/4/11]: Even better, updated interactive example with jsfiddle, added info about $.when