Web Storage
Recently, I had the task of speeding up the response time of an app that depended on a remote API. The issue was that the API could take a long time to respond in some cases, plus four seconds. Four seconds is way to long to wait for a web page… And in this case it was even worse, the data needed to be used for populating an auto-completed list (using the jQuery Autocomplete Widget). At best the result was slow user experience, at worst the auto completion would timeout waiting for the server response and do nothing.
The first suggestion was to have the (Rails) app cache the data. There are plenty of ways to do that, memcache, redis, etc. However, this site is hosted on a well know PAAS (Platform As A Service, there’s a acronym for everything), more moving parts means more cost.
There’s also the issue that this data is per user. That means the cache can grow quickly with unique data.
And, of course, one of the two hardest problems in Computer Science is cache invalidation.
There are two hard things in computer science: cache invalidation, naming things, and off-by-one errors.
— Jeff Atwood (@codinghorror) August 31, 2014
So, data needs to be stored for each user… What if I have the user store the data? No infrastructure needed on server!
The key to this is Web Storage. While Web Storage originally appeared in the HTML5 specification, support for it is widespread even in browsers with no (obvious) HTML5 support, i.e IE8.
Web Storage saves key/value pairs in the browser. It comes in two
flavors, sessionStorage
and localStorage
. sessionStorage
is
cleared when the “session” ends, which is to say when the browser is
closed, in much the same way a cookie without an expiration is
removed. localStorage
is saved indefinitely and persists even after
the browser is closed.
Also like cookies, access to Web Storage is limited to the domain that set it. If a page on example.com sets Web Storage pages served from *.example.com can retrieve it, but pages served from any other domain can not.
Other than the difference in when they are cleared, sessionStorage
and localStorage
are interchangeable, they are both JavaScript
objects you can interact with. The interface is quite simple, there
are three ways to store data:
sessionStorage.timezone = 'America/Denver';
sessionStorage['timezone'] = 'America/Denver';
sessionStorage.setItem('timezone', 'America/Denver');
and three matching ways to retrieve data:
var tz = sessionStorage.timezone;
var tz = sessionStorage['timezone'];
var tz = sessionStorage.getItem('timezone');
Using localStorage
instead of sessionStorage
is simply a matter of
‘s/sessionStorage/localStorage/g’.
It’s important to note that stored values can only be strings (keys can be strings or integers). Fortunately, it’s easy to serialize anything as JSON before storing it.
var states = ['Alive','Unknown','Dead'];
localStorage.setItem('states',states); // Nope!
localStorage.setItem('states',JSON.stringify(states)) // Works!
var states = JSON.parse(localStorage.getItem('states'));
You can remove items from Web Storage with:
sessionStorage.removeItem('timezone');
And you can empty out storage with
sessionStorage.clear();
There are any number of libraries that sit on top of Web Storage, but I’ve yet to find one that somehow made it that the built in methods.
So, given a list of options from a slow API, how does this help me? The trick — using AJAX to populate localStorage asynchronously (presumes jQuery).
$(function(){
things = JSON.parse(localStorage.getItem('things'));
if(!things) {
$.ajax({
url: 'things.json',
dataType: 'json'
})
.done(function( data ) {
things = data;
localStorage.setItem('things',JSON.stringify(things));
});
}
});
When the page loads, the code first checks if it has the data cached in Web Storage. If so, it parses the JSON and makes it available. If not, it fires async AJAX call to get the data. When the call returns, the data is store and made available.
Of course timing is key. The AJAX call may (will) return after the rest of your code has run. The code using the data will need to handle it not yet being available.
As I mentioned, in my case the data was for jQuery UI’s Autocomplete feature and it was hitting the slow JSON endpoint for each character typed. In addition getting data from a URL, Autocomplete can use an array of possible values or a function that returns one.
My approach takes advantage of two features of Autocomplete, first, the data is checked each time a character is entered, second if the array is empty, then Autocomplete does nothing. This solves the timing issue, it doesn’t matter if AJAX call hasn’t completed before the visitor starts typing, As long as I initialize an empty array, Autocomplete won’t fail. One the data is in place, the next character the use types will re-run the matching and auto-completion will begin.
Your use case may be different, but as long you code gracefully handles the state where the data hasn’t arrived yet, you’re covered.
Obviously, this is just the tip of the iceberg for local storage. For example you can use it to cache a thick client’s last known state, allowing it to start working even before it gets online. This is also how “offline” mode works in many web apps.
And now it’s in your toolbox. What will you do with it?
Comments