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 bygetItem()
is the same one that was set usingsetItem()
. - If the
localStorage
feature detect is successful, its result will be truthy. So we append&& localStorage
, which causes thestorage
variable to become a reference to the globallocalStorage
object if the test was successful. In other words, iflocalStorage
isn’t supported,storage === undefined
, which is falsy. If it is supported,storage === window.localStorage
, which is truthy. - Instead of
return
ingfalse
from thecatch
block, wereturn
nothing at all. If an error is caught,storage
will beundefined
, 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 iflocalStorage
is supported and to manipulate it. - By using the local
storage
variable instead of the globallocalStorage
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
tosessionStorage
for your entire web app, you won’t even have to change your variable names. Just replace the one occurence oflocalStorage
withsessionStorage
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?
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 useif (!!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 disablelocalStorage
…localStorage.setItem
,localStorage.removeItem
can throw exceptions! Wrapping it withtry
-catch
may be better.Mathias wrote on :
Paul Neave: For more information about why the
try
-catch
block is there, see the Modernizr source which describes the Firefox issues (the article has this link as well). Update: comment #9 explains why it should be used regardless of browser bugs.P.S. In the snippet you’re using, you could omit the
!!
.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 notundefined
butfalse
, 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 globalstorage
variable, why not avoid all this anonymous function cruft in the first place? :)or even (a little more concise but also more cryptic):
So once this snippet is executed,
storage
is eitherundefined
— if either 1)localStorage
doesn't exist, 2)localStorage.getItem
is falsy, 3)localStorage.getItem
throws — or it references same object aslocalStorage
(if execution got to the assignment part).Mathias 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:
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 returnfalse
instead of letting your code eventually facepalm on the disabled storage (tested in Chrome 12).Paul Irish wrote on :
Paul Neave: I just tested in Aurora with
dom.storage.enabled
set tofalse
(and surprisingly, WAY too many Firefox users do). It no longer throws exceptions when you just tryif (window.localStorage)
so that’s good! It now just returns anull
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.Mathias wrote on :
Paul Irish: IMHO the
try
-catch
should never be removed from this feature test. The Web Storage specification says: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?
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:
Mathias wrote on :
Kurt: Not sure what you’re trying to get at, but you could just do:
However, that still won’t allow you to do e.g.
_userCache.getItem('foo')
iflocalStorage
is disabled or unsupported.I’m not sure how useful that is though, seeing as the
_userCache
object will be lost as soon as you close the page.Paul Irish wrote on :
Recently we changed the test to include a test set/get thanks to iOS 5’s private browsing mode: https://github.com/Modernizr/Modernizr/blob/c56fb8b09515f629806ca44742932902ac145302/modernizr.js#L696-731
Mathias 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, usingif (typeof Storage !== 'undefined')
? Yes, you gotta usetypeof
, otherwise IE 7 throws a fit.Mathias 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 theaddItem
throws an exception, therefore leavingstorage
as truthy. The snippet before that seems fine though. See for proof: https://i.imgur.com/1XW0K9Z.pngMatt Seeley wrote on :
It’s important to remember that this test, and Modernizr, don’t take into account the
setItem
failing becauselocalStorage
is at/near quota. NolocalStorage
or a fulllocalStorage
will both result in a falsystorage
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 thelocalStorage.setItem()
throws an exception. So this seems sufficient (at the top of a function which useslocalStorage
)?Stijn de Witt wrote on :
Some thoughts:
setItem
wrapped in atry…catch
, because of Safari Private Browsing mode.localStorage.setItem
, why not make it complete with one extra check that we actually get back what we stored?localStorage
is not available, it might be easier to use a dummy object instead of wrapping all use of it insideif
blocks.So, without further ado, here is the pattern I am currently using:
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.
Mathias 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.)