JavaScript Cache Provider
Tuesday, July 6th, 2010
Every developer knows the importance of caching. From end to end you have caching on the backend (memcached, xcache, etc.) to prevent your databases being lit on fire, edge caching on content delivery networks (CDN’s) in hopes that your browser will cache assets it sees more than once. And of course client-side caching so you don’t repeat expensive operations (albeit algorithmically or high volume repitions). Here is a solution in JavaScript to help you out with the latter, with optional support for HTML5 Local Storage.
Starting Simple
function CacheProvider() {
// values will be stored here
this._cache = {};
}
Feature detect on local storage
try {
CacheProvider.hasLocalStorage = ('localStorage' in window) && window['localStorage'] !== null;
} catch (ex) {
CacheProvider.hasLocalStorage = false;
}
The main reason we use try / catch is because despite Firefox supporting it, it can be disabled in your about:config settings and an error will be thrown. A simple if / else will not work.
Next we’ll add support for storing objects into local storage. This technique was borrowed from Christopher Blizzard in his excellent post Saving data with local storage – for which those who didn’t know, you can only store string’s into local storage. Thus we have this…
in / out JSON parsing
if (CacheProvider.hasLocalStorage) {
Storage.prototype.setObject = function(key, value) {
this.setItem(key, JSON.stringify(value));
};
Storage.prototype.getObject = function(key) {
return JSON.parse(this.getItem(key));
};
}
Now for our three core methods, we’ll have get, set, and clear.
Core class functionality
CacheProvider.prototype = {
/**
* {String} k - the key
* {Boolean} local - get this from local storage?
* {Boolean} o - is the value you put in local storage an object?
*/
get: function(k, local, o) {
if (local && CacheProvider.hasLocalStorage) {
var action = o ? 'getObject' : 'getItem';
return localStorage[action](k) || undefined;
} else {
return this._cache[k] || undefined;
}
},
/**
* {String} k - the key
* {Object} v - any kind of value you want to store
* however only objects and strings are allowed in local storage
* {Boolean} local - put this in local storage
*/
set: function(k, v, local) {
if (local && CacheProvider.hasLocalStorage) {
if (typeof v !== 'string') {
// make assumption if it's not a string, then we're storing an object
localStorage.setObject(k, v);
} else {
try {
localStorage.setItem(k, v);
} catch (ex) {
if (ex.name == 'QUOTA_EXCEEDED_ERR') {
// developer needs to figure out what to start invalidating
throw new Exception(v);
return;
}
}
}
} else {
// put in our local object
this._cache[k] = v;
}
// return our newly cached item
return v;
},
/**
* {String} k - the key
* {Boolean} local - put this in local storage
* {Boolean} o - is this an object you want to put in local storage?
*/
clear: function(k, local, o) {
if (local && CacheProvider.hasLocalStorage) {
localStorage.removeItem(k);
}
// delete in both caches - doesn't hurt.
delete this._cache[k];
}
};
How does this work?
Note in the beginning of this post, said Cache Provider was to have optional support for local storage (although it appears to be most of the point). First let’s look at an example sans-local-storage.
getElementsByClassName
var cache = new CacheProvider;
window.getElementsByClassName = getElementsByClassName || function(c) {
var reg = cache.get(c) || cache.set(c, new RegExp("(?:^|\\s+)" + c + "(?:\\s+|$)"));
var elements = document.getElementsByTagName('*');
var results = [];
for (var i = 0; i < elements.length; i++) {
if (elements[i].className.match(reg)) {
results.push(elements[i]);
}
}
return results;
};
Note the next time you work with the same class – it work with a precompiled regular expression instead of constructing a new one.
As another example, for large apps that require i18n, you could cache the compiled HTML strings into local storage.
var i18nCache = new CacheProvider;
if (i18nCache.get('topnav')) {
$('#nav').html(i18nCache.get('topnav'));
} else {
ajax('top-nav.tmpl', function(html) {
i18nCache.set('topnav', html);
$('#nav').html(i18nCache.get('topnav'));
});
}
Other than that, have a play, do some caching, and outsource your resources to your users browser. cheers ;)
14 Responses to “JavaScript Cache Provider”
find it
recent
- Autocomplete Fuzzy Matching
- JavaScript Cache Provider
- JavaScript Animate
- Asynchronous method queue chaining in JavaScript
- Something changed
- Unofficial Twitter Widget Documentation
- Twita@talinkahashify your tweets
- Me on Photography and JavaScript
- RegEx Brain Teaser Part II
- Get your Gmail Stickers
- Parenthetical back matching in regular expressions
- The Skinny on Doctypes
i am dustin diaz

July 6th, 2010 at 10:24 am
[...] This post was mentioned on Twitter by Dustin Diaz, Jamie. Jamie said: A Javascript Cache Provider created and explained by @ded (http://bit.ly/aajylg). Optionally uses Local Storage APIs. [...]
July 6th, 2010 at 10:53 am
I like the simplicity, thanks for sharing. I might be overlooking something, but I didn’t see anywhere you used the CacheProvider.re value for noting if a value was an object or string.
July 6th, 2010 at 11:48 am
Brad,
I revised the post – you don’t need them at all actually. As long as you dedicate yourself to using the interface properly by passing in the proper arguments when getting objects from local storage, you should be fine. Hence, the removed code. Hope it works out for you :)
July 6th, 2010 at 12:06 pm
[...] Read the original post: JavaScript Cache Provider [...]
July 7th, 2010 at 2:27 am
[...] Dustin Diaz had the cool idea of providing a cache shim for HTML5 storage [...]
July 7th, 2010 at 6:22 am
But the biggest question to be answered here.. Does it actually help. For bigger and more intensive programs that would probably be yes..
But for your className example I highly doubt checking localStorage, parseJSON and returning a value through a function is faster than re-generating a regexp.
July 7th, 2010 at 9:23 am
Thanks for posting this. I did notice a typo in this line:
if (typeof v !== 'string')) {
July 7th, 2010 at 9:38 am
Arnout,
Fair enough. But the
getElementsByClassexample doesn’t go through local storage (you can’t store regex’s – only strings and JSON). Thus passing through other functions is just doing native function tracing, which would ultimately be faster than constructing new regular expressions.Nevertheless, it was good to point it out and without a doubt should be questioned.
July 7th, 2010 at 11:38 am
I like the idea a lot, and there are plenty of cases where a client side cache would be useful.
return localStorage[action](k) || undefinedWhile this shortcut is easy to understand it has the side effect of changing empty strings, nulls, false, and 0 into undefined. I’m not convinced the “|| undefined” is needed at all.
Looking forward to seeing more cool stuff.
July 7th, 2010 at 10:05 pm
Hi Sean,
Would you reckon just removing the || undefined? This way you can check against !== undefined in the implementation – could work…
July 8th, 2010 at 1:02 am
[...] JavaScript Cache Provider Every developer knows the importance of caching. From end to end you have caching on the backend (memcached, xcache, etc.) to prevent your databases being lit on fire, edge caching on content delivery networks (CDN’s) in hopes that your browser will cache assets it sees more than once. And of course client-side caching so you don’t repeat expensive operations (albeit algorithmically or high volume repitions). Here is a solution in JavaScript to help you out with the latter, with optional support for HTML5 Local Storage. (tags: javascript performance 2010) Scritto il 08/07/10 da giorgio_v. [...]
July 8th, 2010 at 7:22 am
[...] a good idea would be to cache your compiled expressions. This can easily be done using my Cache Provider as [...]
July 8th, 2010 at 9:16 pm
[...] needs modules! JavaScript Cache Provider (Dustin Diaz) Writing Testable [...]
July 12th, 2010 at 11:42 am
Encountered a “Storage is undefined” error in IE8, when the page is using EmulateIE7 mode. This small change seemed to fix it:
CacheProvider.hasLocalStorage = ('localStorage' in window) && window['localStorage'] !== null && typeof Storage !== 'undefined';