Skip to content

Instantly share code, notes, and snippets.

@tbranyen
Created October 25, 2012 02:31
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tbranyen/3950130 to your computer and use it in GitHub Desktop.
Save tbranyen/3950130 to your computer and use it in GitHub Desktop.
Cache Backbone Model and Collection fetch calls with a deferred, simple and intuitive.
/*!
* backbone.cacheit.js v0.1.0
* Copyright 2012, Tim Branyen (@tbranyen)
* backbone.cacheit.js may be freely distributed under the MIT license.
*/
(function(window) {
"use strict";
// Localize global dependency references.
var Backbone = window.Backbone;
var $ = window.$;
var _ = window._;
// Patch the fetch method to retain a reference.
_.each(["Model", "Collection"], function(ctor) {
// Retain a copy of the original fetch method, since we are overidding it.
var oldFetch = Backbone[ctor].prototype.fetch;
// Override both Model and Collection `fetch` methods.
var newFetch = Backbone[ctor].prototype.fetch = function(options) {
options = options || {};
// Save a reference to the original deferred.
var oldDef = this._def;
// Return early.
if (this._def && !options.reload && !this.reload) {
return this._def.promise();
}
// If a deferred doesn't exist, create one. If the clear flag is provided,
// jump in to create a new deferred.
this._def = newFetch.deferred();
// If the clear was provided and there is an existing deferred, resolve it
// once this has resolved.
if (options.reload && oldDef) {
this._def.done(oldDef.resolve);
}
// Call the original `fetch` method and store its return value (jqXHR).
var req = oldFetch.apply(this, arguments);
// Once the request has finished, resolve this deferred.
req.done(_.bind(function() {
this._def.resolveWith(this, [this]);
}, this));
// Return the deferred to wait with.
return this._def.promise();
};
// Allow the jQuery dependency to be swapped out to use this in other
// enviornments.
Backbone[ctor].prototype.fetch.deferred = function() {
return $.Deferred();
};
});
})(this);
@tbranyen
Copy link
Author

Usage

var List = Backbone.Collection.extend({
  url: "/some/api"
});

var list = new List();

// This will fetch AND retain your data.
list.fetch().done(function() {
  console.log(list.models);

  // So that when this is called, you do not fetch over again! :D
  list.fetch().done(function() {
    console.log(list.models):
  });
});

@aaronj1335
Copy link

i'd say it's a great idea -- we use something similar where i work. a couple thoughts:

  • you'll of course need a means of invalidating the cache (we use {reload: true} as a parameter)
  • you'll have to handle ajax calls that return out-of-order (i.e. you make the first fetch, then you call fetch() again and force a reload). this becomes a problem when you're making, say, a single page app and it gets big. there's different ways to handle the situation:
    • do you just never resolve the first deferred?
    • do you resolve the first deferred with the results of the second? (this actually ends up being what our applications typically require)

@tbranyen
Copy link
Author

Just added the option to clear with:

 // Boolean to invalidate cache.
list.fetch(true);

@tbranyen
Copy link
Author

Hrm. nevermind this isn't going to work like that, that'll break the ability to provide options. Definitely like your suggestion @aaronj1335

@tbranyen
Copy link
Author

Cool updated it to do fetch({ reload: true }) instead of fetch(true)

Out of order will work correctly here, its fixed as a byproduct of how i wait until the latest fetch is done before calling resolved on the previous fetch() ;-)

@aaronj1335
Copy link

@tbranyen looks awesome! for completeness 'old' should prolly be an array, and our experience is that you should unit test code like this til kingdom come b/c it's amazing how hard it is to catch all the corner cases.

do you know of any backbone libraries that provide this sort of caching out of the box (I'm thinking of stuff like supermodel here)?

@tbranyen
Copy link
Author

I dunno, this was an idea that came out of #backbone-boilerplate on irc. Also I just made a project page: https://github.com/tbranyen/backbone.cacheit.js

@tbranyen
Copy link
Author

@addyosmani
Copy link

Nice work Tim! Appreciate you documenting this well in the repo. Might include it in Aura as we've been thinking of doing something similar for the Backbone extension :)

@bobholt
Copy link

bobholt commented Oct 25, 2012

Glad to see this. Many of my models only need to fetch, and they do that from a fairly static data set, so I've had to do something similar. Nice to see a concise solution we can all develop and share.

@kalebdf
Copy link

kalebdf commented Oct 25, 2012

Great solution, Tim! Thanks.

I've done a bit of work with a CachingModel mixin which attempts to address HTTP ETag caching / 304 Not-Modified responses. At some point it would be amazing to integrate with this.

@gabrielengel
Copy link

Very useful! Thanks! :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment