RSS Feed

Almacenando en el cliente: LocalStorage, SessionStorage y Cookies

7

diciembre 20, 2012 by - @pjnovas

Seguramente ya escuchaste sobre el almacenamiento local de HTML5, la intensión de este post es pegarle un vistazo para que empieces a usarlo.

Primero tenemos que tener en cuenta que LocalStorage es el almacenamiento que no expira, y SessionStorage es el que vive sólo en una sesión.
Ambos tienen los mismos métodos:

  • getItem ( key )
  • setItem ( key , value )
  • removeItem ( key )

Van a ver por ahí un globalStorage: es una implementación de Mozilla previa a HTML5, pero desde la versión de Firefox 13 dejó de soportarse, por lo que olvidate de que existe ;)

No hay mucho para explicar sobre los métodos ya que hablan por si solos, veamos un ejemplo:

if (window.localStorage) {

  localStorage.setItem("nombre", "pepe");

  var nombre = localStorage.getItem("nombre");
  
  localStorage.removeItem("nombre");
}
else {
  throw new Error('Tu Browser no soporta LocalStorage!');
}

También se puede utilizar los Items como propiedades del objeto localStorage ó sessionStorage (pero no está recomendado, así que tomalo como título informativo):

localStorage["nombre"] = "pepe";
var nombre = localStorage.nombre;
delete localStorage["nombre"];

El soporte de navegadores es muy amplio:

+----------------+--------+-----------------+----+-------+-----------------+
|    Feature     | Chrome | Firefox (Gecko) | IE | Opera | Safari (WebKit) |
+----------------+--------+-----------------+----+-------+-----------------+
| localStorage   |      4 | 3.5             |  8 | 10.50 |               4 |
| sessionStorage |      5 | 2               |  8 | 10.50 |               4 |
+----------------+--------+-----------------+----+-------+-----------------+

Pueden ver mas info acá

Y cuánto podemos almacenar?: depende mucho del navegador, pero la cantidad oscila entre 2.5 y 5 Mb (2.5 Mb en la mayoría de los navegadores, lo cual es bastante ;) )
Pueden ver la tabla completa y probar cuanto se banca el navegador en el que están en Web Storage Test

Podemos guardar JSON?, Si, pero yo te recomendaria que sea serializado, (en String) y lo que hagas antes de enviarlo.
Por que esto?

  1. Por los “fallbacks/ polyfills”, si queremos agregarle un soporte a cookies por si no tiene disponible el web storage, puede que tengamos problemas si no estamos manejando strings.
  2. Lo serializa de todas formas, pero la diferencia es que no es automático, por lo que podemos tener comportamientos extraños:
  localStorage.setItem("locura", true);
  var locura = localStorage.getItem("locura");
  console.log(locura); // true
  console.log(typeof locura); // 'string'

Ahora, con JSON?, supongamos que tenemos:

var persona = {
    nombre: "pepe",
    edad: 20,
    locura: true
};

Guardamos directo el JSON al localStorage:

localStorage.setItem("persona", persona);
var personaGuardada = localStorage.getItem("persona");

console.log(typeof persona); //object
console.log(typeof personaGuardada); //string

console.log(personaGuardada.locura); //undefined!
var personaGuardada = JSON.parse(personaGuardada); //Uncaught SyntaxError

Como habría que hacerlo:

var personaAGuardar = JSON.stringify(persona);

localStorage.setItem("persona", personaAGuardar);
var personaGuardada = localStorage.getItem("persona");

console.log(typeof persona); //object
console.log(typeof personaGuardada); //string

var personaGuardada = JSON.parse(personaGuardada); 
console.log(personaGuardada.locura); //true

Por último te dejo un fallback a Cookies, ya que no siempre tenemos soporte para usarlo, está es una implementacion de Mozilla bastante coqueta (no se si funciona en IE 7, por ejemplo), la idea es que en nuestra aplicación usamos directamente window.localStorage y siempre va a existir, si el browser no lo soporta va a ir a cookies automáticamente.
Fuente: MDN – DOM Storage

if (!window.localStorage) {
  Object.defineProperty(window, "localStorage", new (function () {
    var aKeys = [], oStorage = {};
    Object.defineProperty(oStorage, "getItem", {
      value: function (sKey) { return sKey ? this[sKey] : null; },
      writable: false,
      configurable: false,
      enumerable: false
    });
    Object.defineProperty(oStorage, "key", {
      value: function (nKeyId) { return aKeys[nKeyId]; },
      writable: false,
      configurable: false,
      enumerable: false
    });
    Object.defineProperty(oStorage, "setItem", {
      value: function (sKey, sValue) {
        if(!sKey) { return; }
        document.cookie = escape(sKey) + "=" + escape(sValue) + "; expires=Tue, 19 Jan 2038 03:14:07 GMT; path=/";
      },
      writable: false,
      configurable: false,
      enumerable: false
    });
    Object.defineProperty(oStorage, "length", {
      get: function () { return aKeys.length; },
      configurable: false,
      enumerable: false
    });
    Object.defineProperty(oStorage, "removeItem", {
      value: function (sKey) {
        if(!sKey) { return; }
        document.cookie = escape(sKey) + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/";
      },
      writable: false,
      configurable: false,
      enumerable: false
    });
    this.get = function () {
      var iThisIndx;
      for (var sKey in oStorage) {
        iThisIndx = aKeys.indexOf(sKey);
        if (iThisIndx === -1) { oStorage.setItem(sKey, oStorage[sKey]); }
        else { aKeys.splice(iThisIndx, 1); }
        delete oStorage[sKey];
      }
      for (aKeys; aKeys.length > 0; aKeys.splice(0, 1)) { oStorage.removeItem(aKeys[0]); }
      for (var aCouple, iKey, nIdx = 0, aCouples = document.cookie.split(/\s*;\s*/); nIdx < aCouples.length; nIdx++) {
        aCouple = aCouples[nIdx].split(/\s*=\s*/);
        if (aCouple.length > 1) {
          oStorage[iKey = unescape(aCouple[0])] = unescape(aCouple[1]);
          aKeys.push(iKey);
        }
      }
      return oStorage;
    };
    this.configurable = false;
    this.enumerable = true;
  })());
}


  • matiasarriola

    Muy bueno!

    Las contras de usar un polyfill que termina almacenando en cookies me imagino que deben ser las siguientes:
    * Límite de almacenamiento muy limitado ( ~ 4kb )
    * La información va a ser envíada en cada request al servidor innecesariamente
    * Maneja el caso de que la clave/valor contengan punto y coma “;” ?

    Bueno dejo un par de soluciones de storage más que pueden encontrar en la wiki de Modernizr https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills (En la parte de Web Storage)

  • Efraingb

    Amigo y qué tanto puedo tener en storage?? Sólo datos simples?? O puedo descargar archivos con código HTML?? Te explico. Y te agradecería enormemente cualquier ayuda.
    Tenemos un sitio Web algo grande. Tiene un archivo de Index que es el único que en todo momento se le muestra al usuario. Fuera de eso, hay muchos archivos que tienen diferentes divs, estos se pasan al index cuando se requieren y ahí se arma lo que se presenta en pantalla. Esto se hace a través de form posts en donde se envían varios input hidden que contienen varios datos diferentes, incluyendo algunos datos del usuario pero en especial los archivos con los divs que se requieren en ese momento. El problema es que a como está este código pues el servidor se exige al máximo en los picos de tráfico. Pensé que debía de haber alguna forma de hacer parte importante del trabajo mejor en la computadora del cliente. Y así llegué aquí. Pensé que con javascript quizá podría descargar todos estos archivos junto muchos de los datos cuando la persona se loguea. Y luego cuando la persona recorre las páginas del sitio pues con javascript va cargando el código html que se requiera. Pero aún si eso es posible, no tengo idea de cómo hacerlo. Algún consejo amigo?? Cualquier comentario es bienvenido.

    • matiasarriola

      Hola, con localStorage sólo podés almacenar strings. Es decir, que todo valor va a intentar ser convertido automáticamente a string. Si querés guardar un objeto, lo vas a tener que serializar (JSON.stringify, etc) o sino vas a terminar guardando el típico “[object Object]”; y para recuperar el valor necesitás desserializarlo.

      A nivel limitación de espacio, tenés entre 2 y 5 megas por dominio, según el browser.

      Podrías llegar a guardar fragmentos de html, y luego insertarlos, pero lo veo más como un “hack” que como una solución apropiada, sería cuestión de probarlo. Hay que tener en cuenta que los procesos de serialización/desserialización y salvado/recuperado de los datos pueden llegar a ser bastante pesados, llegando a causar algún que otro bloqueo de UI si se abusa de éstos.

      Entiendo en rasgos generales tu problema, y creo que podrías llegar a cachear resultados de la requests para no tener que repetirlas ( si es que los resultados no varían ) – en la práctica debe ser uno de los principales usos de localStorage. Lo que quiero destacar es que lo que importa es la *forma*. Para eso hace falta un análisis más profundo, y ver tambien la flexibilidad que tenés para poder adaptar tu código server side para tener una mejor solución a nivel general. (http cache? responder json en lugar de html? appcache http://www.html5rocks.com/es/tutorials/appcache/beginner/ ?, etc etc etc)

      Si te llevás bien con el inglés acá hay algunos recursos más:

      http://localstorage-use-not-abuse.appspot.com/
      http://programmers.stackexchange.com/questions/105524/is-it-realistic-to-make-use-of-html5-local-storage-to-store-css-and-javascript

      Espero que mi respuesta sirva de algo, ojalá alguien más deje sus opiniones :D

      • Efraingb

        De mucho. Gracias

        • Mauricio

          Lo que describis es el caso de una “Single Page Application”, AngularJS es un framework que te podría resultar muy útil en el escenario que planteas (mirá los conceptos de vistas, routing, template, templateCache, etc).

  • miguel velarde

    Hola. El sessionStorage me funciona en localhost pero al subirlo a un servidor y acceder al sitio desde fuera de mi red (via internet) sessionStorage no me funciona. Al abrir la ventana de recursos no me aparece nada almacenado en SessionStorage. Local Storage si me funciona pero Session Storage no. ¿hay algo que deba configurar del lado del browser o del servidor? Utilizo IIS 7.

    • matiasarriola

      Hola Miguel, tanto localStorage como sessionStorage están 100% del lado del cliente, así que no se necesita tener nada del lado de servidor para esto. La verdad que con la información que das, no se me ocurre por qué no te funciona, sólo te recomiendo que te fijes bien el tema del dominio (es decir hacer setItem y getItem desde el mismo dominio y en la misma sesión).