Recently we’ve had the opportunity to use LocalStorage for our content. I quite liked it at first because it was very simple to use, but it wasn’t long before we ran into issues. Get this: LocalStorage stores content indefinitely, while SessionStorage stores content for the lifetime of the browser session… and there’s no middle ground. What if I want to store something for 30 minutes? Tough luck!
To make things more interesting, even though some browsers report that they have LocalStorage (I’m looking at you, Safari in Private Browsing mode!) you can’t actually use it (trying to write to/read from it will throw an exception).
Bearing these in mind, we decided to write a small custom library that gives you the ability to use expiries, fail-safe LocalStorage detection, and callbacks for when fetching data doesn’t run as expected.
Just want to see the final result? Here it is.
Let’s run through it!
Our most important method is supportsLocalStorage().
This method makes sure we can actually use LocalStorage, bypassing the likes of our aforementioned friend, Safari in Private Browsing mode. Here we wrap the LocalStorage set/get in a try/catch statement to catch any read/write exceptions.
Then, we call supportsLocalStorage() at the start of all the other methods to ensure we can actually use LocalStorage. If not, just abort any call early.
Now let’s look at the setter, setItem():
The last parameter, expiry, is an optional numeric parameter that if supplied, is used as the lifespan of your key/value combination. When combined with our LocalStorage getter, we can use this expiry value to know whether we should bust the key/value next time we go to fetch it!
Be warned: there’s not a tremendous amount of validation ensuring legitimate numbers are used as an expiry – just provide an integer for the number of seconds you want your key/value combination remaining valid in the LocalStorage cache. It’s easy, you’ll be fine.
You’ll notice this method returns true without checking whether your item was actually set in LocalStorage. Unfortunately the API itself doesn’t return a “save” response, so we have to assume the best and say that it did. But if this isn’t good enough, you can write a fetch method to confirm your suspicions. To me, that seems like overkill because the ideal time to check your item is when you’re actually fetching the data to use it. LocalStorage, like cookies, can be easily modified by the end user, so don’t assume that just because you saved it, it’ll be there the next time you go to pull it.
Our next very important method is our getter, getItem()!
This method is a little more complex. Obviously, the first parameter is the key name for the value you want to retrieve. The second value, however, is a callback you can provide to handle when a value doesn’t exist in LocalStorage. While a callback isn’t technically necessary, because LocalStorage calls aren’t asynchronous, I prefer using them as I feel it makes my code neater.
In your closure, your value in LocalStorage will be returned if it exists. Otherwise, if it doesn’t exist or if it has expired, you’ll get a null return value. This was the cleanest way I could come up with for handling both scenarios, which unfortunately does mean having to do an if/else statement in the closure to check whether the response is null or not.
Finally, our last method is a simple remover method.
The native LocalStorage method doesn’t return a status on whether the operation was successful or not, so I mimic the core method by returning void. Again, you could use a getter to ensure data has been removed, but I don’t really see a point in ensuring the core method has worked correctly.
Now that we’ve run through the class and its methods, here it is in its entirety: https://bitbucket.org/snippets/humaanco/gKL7x
Both of these examples use the expiry feature in the LocalStorage helper. If the devices key/value doesn’t exist in LocalStorage, we’ll fetch it via AJAX and save it to LocalStorage for 1 hour (60 minutes), then either pass it off to another function or return it.
And there you have it! Check the Bitbucket snippet link for the latest updates as we continue to use and refine our LocalStorage helper.
30th May, 2016: added try/catch block for catching DOMException errors (Safari doesn’t seem to like values larger than 1.5 MB)