This post originally appeared on Engine Yard.
It was also published on the Quick Left Blog.
Introduction
I was working on a Backbone project with Bob Bonifield recently, when we came across a problem. We were building an administration panel to be used internally by a client, and there were a couple of views that needed to display the same information about users in a slightly different way. To prevent unnecessary AJAX calls, we decided to cache the result of Backbone.Model#fetch
at two levels of our application.
The result: less network time and a snappier user experience.
Here’s how we did it.
Caching the Controller Call
We decided to use Brent Ertz’s backbone-route-control package. It makes separation of concerns in the Backbone router easier by splitting the methods associated with each route into controllers.
I’ll show how we set up the UsersController
to handle the first level of caching. In this example, we’ll use backbone-route-control
. If you weren’t using it, you could accomplish the same thing in the Backbone router.
First, we set up the app view and initialized a new Router as a property of it, passing in the controllers we wanted to use.
1 2 3 4 5 6 7 8 9 10 11 |
|
Next, we defined the router and set it up to use the UsersController to handle the appropriate routes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Caching the User at the Controller Level
After we got the router set up, we defined the UsersController and the appropriate route methods. We needed to wait until the user was loaded before we could generate the DOM, because we needed to display some data about the user.
We opted to cache the ID of the last user that was accessed by either the show
or dashboard
method, so that we wouldn’t repeat the fetch call when we didn’t need to. We set the result of the call to Backbone.Model#fetch
(a promise) to a variable called userLoadedDeferred
, and passed it down the the views themselves.
In doing so, we took advantage of the fact that, behind the scenes, fetch
uses jQuery.ajax and returns a deferred object. When saving the result of a call to jQuery.ajax
to variable, the value of the deferred’s .complete
or .fail
callback will always return the same payload after it has been fetched from the server.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
|
Caching the User at the Model Level
Although our UsersController
was now caching the result of a fetch for a given user, we soon found that also needed to refetch the user to display their information in a sidebar view as well.
Since the UsersController
and the SidebarView
were making two separate calls to the User
model fetch
method, we decided to do some more caching in the Backbone Model. We opted to save the results of the fetch
call for 30 seconds, only making a new server request if the timer had expired.
This allowed us to simply call fetch
from within the view, without needing to know whether the User
model was making an AJAX call or just returning the cached user data.
Here’s what the code looked like in the model:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
|
Busting the Cache
Later on in our development, we came across a situation where we needed to force a new fetch of User
, right after updating some of their attributes. Because we were caching the result for 30 seconds, the newly updated attributes were not getting pulled from the server on our next fetch call. To overcome this, we neede to bust our cache manually. To make this happen, we changed our overridden fetch
method to take an option that allowed us to force a refetch.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Conclusion
Caching the User
model in this app reduced our network time by quite a bit. Initially, we were making two server calls per route, because we had to fetch the user to display data in both the main view and the sidebar. After saving the result of the fetch
in the controller, we were now only calling to the server once per User ID.
With the addition of model-level caching, we were also able to remove the duplicated call between the main views and the sidebar view, by saving the results of the fetch
call for 30 seconds.
Overall, we reduced four calls per route to one call per 30 seconds. Making these adjustments helped make our application behave a lot more smoothly, and reduced server load in the process.
P.S. Have you implemented anything like this before? What are some of the tricks you use to make Backbone more efficient? Tweet at me @fluxusfrequency.