Mathias Bynens

How I detect and use localStorage: a simple JavaScript pattern

Published · tagged with JavaScript, performance

Disclaimer: This is not a localStorage tutorial — see Chapter 7 of Dive Into HTML5 if that’s what you’re looking for. Read on if you’re still interested.

The conventional, old-school method

Here’s how you can detect localStorage support:

// Feature test
var hasStorage = (function() {
try {
localStorage.setItem(mod, mod);
localStorage.removeItem(mod);
return true;
} catch (exception) {
return false;
}
}());

Note that accessing the global localStorage object may throw an exception, so it needs to be done inside a try-catch block for this test.

Modernizr does it this way (and documents the technique extensively in the source). It could be simplified a bit depending on the browsers and edge cases you wish to support, but for now this is the most robust feature detect out there.

With the above snippet, you can make use of localStorage in your code as follows:

if (hasStorage) {
// Use `localStorage` here, e.g.
localStorage.setItem('foo', 'bar');
localStorage.lol = 'wat';
localStorage.removeItem('foo');
}

Of course, this is nothing new. So why am I writing this? Well, I’ve been using a slightly different technique that has a few advantages, and I’d like to share it.

The new pattern

Let’s see what happens if we tweak the above script just a little bit:

// Feature detect + local reference
var storage = (function() {
var uid = new Date;
var result;
try {
localStorage.setItem(uid, uid);
result = localStorage.getItem(uid) == uid;
localStorage.removeItem(uid);
return result && localStorage;
} catch (exception) {}
}());

Compared to the previous snippet, not much has changed. There are three differences:

  • This snippet actually detects if localStorage works correctly, by checking if the value that is returned by getItem() is the same one that was set using setItem().
  • If the localStorage feature detect is successful, its result will be truthy. So we append && localStorage, which causes the storage variable to become a reference to the global localStorage object if the test was successful. In other words, if localStorage isn’t supported, storage === undefined, which is falsy. If it is supported, storage === window.localStorage, which is truthy.
  • Instead of returning false from the catch block, we return nothing at all. If an error is caught, storage will be undefined, which is falsy.

We could take it one step further and avoid the repeated scope lookups for localStorage (if it’s available) as follows:

// Feature detect + local reference
var storage = (function() {
var uid = new Date;
var storage;
var result;
try {
(storage = window.localStorage).setItem(uid, uid);
result = storage.getItem(uid) == uid;
storage.removeItem(uid);
return result && storage;
} catch (exception) {}
}());

As Juriy “kangax” Zaytsev noted, it’s possible to avoid the anonymous function entirely:

// Feature detect + local reference
var storage;
var fail;
var uid;
try {
uid = new Date;
(storage = window.localStorage).setItem(uid, uid);
fail = storage.getItem(uid) != uid;
storage.removeItem(uid);
fail && (storage = false);
} catch (exception) {}

It works just as well, and is even more efficient than the previous snippet because of the reduced number of function calls.

Anyhow, you can use any of these snippets like this:

if (storage) {
// Use `storage` here, e.g.
storage.setItem('foo', 'bar');
storage.lol = 'wat';
storage.removeItem('foo');
}

It couldn’t be simpler!

Useful?

I mostly like this pattern because of its elegance, but — assuming your code is inside its own scope to prevent leaking variables to the global scope — there are some (minor) performance benefits as well:

  • There’s only a single variable (storage) — and it can be used both to check if localStorage is supported and to manipulate it.
  • By using the local storage variable instead of the global localStorage object directly, scope lookups are kept to a minimum.
  • Consistently using a local variable that’s referencing localStorage results in a smaller file size after compression/minification.
  • It’s an edge case, but if you ever need to switch from localStorage to sessionStorage for your entire web app, you won’t even have to change your variable names. Just replace the one occurence of localStorage with sessionStorage and you’re done. (These APIs are identical, and even the feature detect can be done the same way.)

Other use cases

I’ve been using localStorage (and sessionStorage) in this example, but of course the exact same pattern could be applied in other cases as well. Can you think of other examples?

About me

Hi there! I’m Mathias. I work on Chrome DevTools and the V8 JavaScript engine at Google. HTML, CSS, JavaScript, Unicode, performance, and security get me excited. Follow me on Twitter, Mastodon, and GitHub.

Comments

Paul Neave wrote on :

I think the try-catch block is only for a bug in Firefox 4 beta, which has long been squashed. I simply use if (!!localStorage.getItem) which works well enough for me.

4esn0k wrote on :

Paul Neave: Spec says, that implementations may throw error, when accessing window.localStorage and user disable localStorage

localStorage.setItem, localStorage.removeItem can throw exceptions! Wrapping it with try-catch may be better.

Paul Neave wrote on :

Mathias: Yes, I read the Modernizr source — I think they’re being overcautious for the sake of Firefox 4 beta (which nobody uses anymore). I use the !! as a way of “truthyfying” the statement a bit more, so it’s not undefined but false, and can be stored in a variable like you suggested.

No harm in being overcautious, but it’s an edge case for me.

4esn0k: I’d like to see which browsers actually do break when being tested for local and session storage. AFAIK it’s only Firefox 4 beta that breaks?

kangax wrote on :

If you’re aliasing localStorage to global storage variable, why not avoid all this anonymous function cruft in the first place? :)

try {
if (localStorage.getItem) {
var storage = localStorage;
}
} catch (exception) { }

or even (a little more concise but also more cryptic):

try { 
localStorage.getItem && (window.storage = localStorage);
} catch (exception) { }

So once this snippet is executed, storage is either undefined — if either 1) localStorage doesn't exist, 2) localStorage.getItem is falsy, 3) localStorage.getItem throws — or it references same object as localStorage (if execution got to the assignment part).

wrote on :

kangax: I’m assuming the code is inside its own scope (to prevent leaking variables to the global scope), so storage isn’t supposed to be global. That said, your first suggestion would still work fine in a local scope! I’ve amended the article. Thanks!

John-David Dalton wrote on :

Update by Mathias: This comment was in response to the feature test that Modernizr was using before, which was basically something like:

// Feature test
var hasStorage = (function() {
try {
return !!localStorage.getItem;
} catch (exception) {
return false;
}
}());

Modernizr later switched to a more robust feature detect instead (similar to the one John-David suggested here), and this post has since been rewritten.


Speaking of localStorage being disabled… This snippet will determine if it’s disabled and return false instead of letting your code eventually facepalm on the disabled storage (tested in Chrome 12).

var storage = !!function() {
var result;
var uid = +new Date;
try {
localStorage.setItem(uid, uid);
result = localStorage.getItem(uid) == uid;
localStorage.removeItem(uid);
return result;
} catch (exception) {}
}() && localStorage;

Paul Irish wrote on :

Paul Neave: I just tested in Aurora with dom.storage.enabled set to false (and surprisingly, WAY too many Firefox users do). It no longer throws exceptions when you just try if (window.localStorage) so that’s good! It now just returns a null object, which is pleasant. Dunno about Fx4, but I don’t think the superfluous exceptions got resolved in the Fx4 beta cycle… Not sure…

Anyway we’ll keep an eye on it and hope to remove the try/catch soon.

wrote on :

Paul Irish: IMHO the try-catch should never be removed from this feature test. The Web Storage specification says:

The user agent may throw a SECURITY_ERR exception instead of returning a Storage object if the request violates a policy decision (e.g. if the user agent is configured to not allow the page to persist data).

[…]

If the Document’s origin is not a scheme/host/port tuple, then throw a SECURITY_ERR exception and abort these steps.

[…]

User agents must raise a SECURITY_ERR exception whenever any of the members of a Storage object originally returned by the localStorage attribute are accessed by scripts whose effective script origin is not the same as the origin of the Document of the Window object on which the localStorage attribute was accessed. This means Storage objects are neutered when the document.domain attribute is used.

Kurt wrote on :

Thank you.

I am working on an implementation myself. One thought is to have it return an array should it otherwise be false. As I don’t care where this data is being persisted, I will always have a storage object to work with.

Do you foresee any issues with such approach?

var _userCache = ((function() {
try {
return localStorage.getItem;
} catch (exception) {}
}()) && localStorage) || {};

Kurt wrote on :

I realize my example was somewhat, to say the least, contrived. But, the idea was to have a single object without having to create a set of wrapper functions — which doesn’t make any sense now that I think about it. I need to implement a set of wrappers regardless. What can I say… It’s Friday afternoon!

Kurt wrote on :

I actually did find a way in the end, which has been working in a prototype:

var _userCache = (function() {
try {
if (localStorage.getItem) {
return localStorage;
}
} catch (exception) {
return {};
}
}()) && localStorage || {};

wrote on :

Kurt: Not sure what you’re trying to get at, but you could just do:

var _userCache = (function() {
try {
var storage = localStorage;
return storage.getItem ? storage : {};
} catch (e) {
return {};
}
}());

However, that still won’t allow you to do e.g. _userCache.getItem('foo') if localStorage is disabled or unsupported.

// Assume `localStorage` is disabled or unsupported
_userCache.setItem('foo', 'bar'); // ReferenceError
_userCache.foo = 'bar';
_userCache.getItem('foo'); // ReferenceError
_userCache.foo;
_userCache.removeItem('foo'); // ReferenceError
delete _userCache.foo;
_userCache.clear(); // ReferenceError
_userCache = {};

I’m not sure how useful that is though, seeing as the _userCache object will be lost as soon as you close the page.

wrote on :

Paul: Thanks for the heads up! Since this post used the Modernizr feature detect as its base example, I’ve now rewritten the whole thing to use the updated code.

Phil L wrote on :

Why not just test for localStorage first, using if (typeof Storage !== 'undefined')? Yes, you gotta use typeof, otherwise IE 7 throws a fit.

wrote on :

Phil: That’s a way to use feature detection. The snippet this blog post is about performs feature testing which is more robust. For example, an iOS device with private browsing mode enabled would pass your test, even though you can’t actually use localStorage in those conditions.

Simon wrote on :

The final update about removing the anonymous function seems to break it (iOS7 Safari Private mode) because (storage = window.localStorage) succeeds, but the addItem throws an exception, therefore leaving storage as truthy. The snippet before that seems fine though. See for proof: https://i.imgur.com/1XW0K9Z.png

Matt Seeley wrote on :

It’s important to remember that this test, and Modernizr, don’t take into account the setItem failing because localStorage is at/near quota. No localStorage or a full localStorage will both result in a falsy storage value.

Liz Verano wrote on :

This post helps me alot. Now, I can detect and use localstorage which will works well.

You can also check this out for more additional ideas: http://cdmckay.org/blog/2014/09/12/finding-out-the-size-of-localstorage/

lorna wrote on :

I’m not clear why the set/get match is required; which case does that cover? For example, if the browser has localStorage support but the user has somehow disabled that (explicitly in their preferences or via private browsing) then the localStorage.setItem() throws an exception. So this seems sufficient (at the top of a function which uses localStorage)?

function myLocalStorageFunc() {
try {
var uid = new Date;
localStorage.setItem(uid, uid);
localStorage.removeItem(uid);
} catch (exception) {
alert('some text');
return;
}
// Code that uses localStorage goes here.
}

Stijn de Witt wrote on :

Some thoughts:

  1. You should definitely actively call setItem wrapped in a try…catch, because of Safari Private Browsing mode.
  2. If we are going to test functionality of localStorage.setItem, why not make it complete with one extra check that we actually get back what we stored?
  3. When localStorage is not available, it might be easier to use a dummy object instead of wrapping all use of it inside if blocks.
  4. If we go with a dummy object, why not make it functional?

So, without further ado, here is the pattern I am currently using:

var DB;
try {
var x = 'test_localstorage_available_' + Date.now();
localStorage.setItem(x, x);
var y = localStorage.getItem(x);
localStorage.removeItem(x);
if (x !== y) {throw new Error();}
DB = localStorage; // Yippee, all is fine!
} catch (exception) {
DB = new MemoryStorage('my-cool-app'); // fall back to shim
}

memorystorage is a little library that I wrote that implements the Web Storage API in memory and can be used as a drop-in replacement for localStorage. No data will survive page-reload, but it would be wiped after the browsing session anyway in Private Browsing Mode.

For more details, see my blog post on this topic: Introducing MemoryStorage.

wrote on :

Stijn: I’m confused — the pattern in this blog post is already doing what you’re suggesting in points 1 and 2.

(Points 3 and 4 are subjective.)

Leave a comment

Comment on “How I detect and use localStorage: a simple JavaScript pattern”

Your input will be parsed as Markdown.