Como qualquer outro ambiente de programação, você precisa de um local para armazenar seus dados ao codificar no navegador com JavaScript. Além de simples variáveis ​​JavaScript, há uma variedade de opções que variam em sofisticação, desde usar localStorage para biscoitos para IndexedDB e a API de cache do service worker. Este artigo é uma rápida pesquisa sobre os mecanismos comuns de armazenamento de dados em seus programas JavaScript.

Variáveis ​​JavaScript

Você provavelmente já está familiarizado com o conjunto de tipos de variáveis ​​altamente flexíveis do JavaScript. Não precisamos revisá-los aqui; eles são muito poderosos e capazes de modelar qualquer tipo de dados, desde os números mais simples até gráficos e coleções cíclicas intrincadas.

A desvantagem de usar variáveis ​​para armazenar dados é que elas ficam confinadas à vida do programa em execução. Quando o programa termina, as variáveis ​​são destruídas. Claro, eles podem ser destruídos antes o programa termina, mas a variável global de maior duração desaparecerá com o programa. No caso do navegador da web e de seus programas JavaScript, até mesmo um único clique no botão atualizar aniquila o estado do programa. Este fato impulsiona a necessidade de dados persistência; isto é, dados que sobrevivem à vida do próprio programa.

Uma complicação adicional com o JavaScript do navegador é que ele é um ambiente em área restrita. Não tem acesso direto ao sistema operacional porque não está instalado. Um programa JavaScript depende da agência das APIs do navegador em que é executado.

Salvando dados no servidor

A outra extremidade do espectro do uso de variáveis ​​integradas para armazenar objetos de dados JavaScript é enviar os dados para um servidor. Você pode fazer isso facilmente com um fetch() POST solicitar. Desde que tudo funcione na rede e na API de back-end, você pode confiar que os dados serão armazenados e disponibilizados no futuro com outro GET solicitar.

Até agora, estamos escolhendo entre a transitoriedade das variáveis ​​e a permanência da persistência do lado do servidor. Cada abordagem tem um perfil particular em termos de longevidade e simplicidade. Mas vale a pena explorar algumas outras opções.

API de armazenamento na Web

Existem dois tipos de “armazenamento web” integrado em navegadores modernos: localStorage e sessionStorage. Isso fornece acesso conveniente a dados de longa duração. Ambos fornecem um valor-chave e cada um tem seu próprio ciclo de vida que rege como os dados são tratados:

  • localStorage salva um par de valores-chave que sobrevive durante o carregamento de páginas no mesmo domínio.
  • sessionStorage funciona de forma semelhante a localStorage mas os dados duram apenas enquanto a sessão da página.

Em ambos os casos, os valores são forçados a uma string, o que significa que um número se tornará uma versão de string de si mesmo e um objeto se tornará “(object Object).” Obviamente não é isso que você deseja, mas se quiser salvar um objeto, você sempre pode usar JSON.stringify() e JSON.parse().

Ambos localStorage e sessionStorage usar getItem e setItem para definir e recuperar valores:


localStorage.setItem("foo","bar");
sessionStorage.getItem("foo"); // returns “bar”

Você pode ver mais claramente a diferença entre os dois definindo um valor para eles e fechando a guia do navegador, reabrindo uma guia no mesmo domínio e verificando seu valor. Valores salvos usando localStorage ainda existirá, enquanto sessionStorage será nulo. Você pode usar o console devtools para executar este experimento:


localStorage.setItem("foo",”bar”);
sessionStorage.setItem("foo","bar");
// close the tab, reopen it
localStorage.getItem('bar2'); // returns “bar”
sessionStorage.getItem("foo") // returns null

Biscoitos

Enquanto localStorage e sessionStorage estão vinculados à página e ao domínio, os cookies oferecem uma opção de vida mais longa vinculada ao próprio navegador. Eles também usam pares de valores-chave. Os cookies já existem há muito tempo e são usados ​​para uma ampla variedade de casos, incluindo aqueles que nem sempre são bem-vindos. Os cookies são úteis para rastrear valores em domínios e sessões. Eles têm prazos de validade específicos, mas o usuário pode optar por excluí-los a qualquer momento, limpando o histórico do navegador.

Os cookies são anexados às solicitações e respostas ao servidor e podem ser modificados (com restrições regidas por regras) tanto pelo cliente quanto pelo servidor. Bibliotecas úteis como JavaScript Cookie simplificam o tratamento de cookies.

Os cookies são um pouco estranhos quando usados ​​diretamente, o que é um legado de suas origens antigas. Eles são definidos para o domínio no document.cookie propriedade, em um formato que inclui o valor, o tempo de expiração (no formato RFC 5322) e o caminho. Se nenhuma expiração for definida, o cookie desaparecerá após o navegador ser fechado. O caminho define qual caminho no domínio é válido para o cookie.

Aqui está um exemplo de configuração de um valor de cookie:


document.cookie = "foo=bar; expires=Thu, 18 Dec 2024 12:00:00 UTC; path=/";

E para recuperar o valor:


function getCookie(cname) {
  const name = cname + "=";
  const decodedCookie = decodeURIComponent(document.cookie);
  const ca = decodedCookie.split(';');
  for (let i = 0; i < ca.length; i++) {
    let c = ca(i);
    while (c.charAt(0) === ' ') {
      c = c.substring(1);
    }
    if (c.indexOf(name) === 0) {
      return c.substring(name.length, c.length);
    }
  }
  return "";
}
const cookieValue = getCookie("foo");
console.log("Cookie value for 'foo':", cookieValue);

No acima, usamos decodeURIComponent para descompactar o cookie e depois quebrá-lo ao longo de seu caractere separador, o ponto e vírgula (;), para acessar suas partes componentes. Para obter o valor, combinamos o nome do cookie mais o sinal de igual.

Uma consideração importante com relação aos cookies é a segurança, especificamente ataques de cross-site scripting (XSS) e cross-site request forgery (CSRF). (Definir HttpOnly em um cookie torna-o acessível apenas no servidor, o que aumenta a segurança, mas elimina a utilidade do cookie no navegador.)

Banco de dados indexado

IndexedDB é o armazenamento de dados no navegador mais elaborado e capaz. Também é o mais complicado. IndexedDB usa chamadas assíncronas para gerenciar operações. Isso é bom porque permite evitar o bloqueio do thread, mas também proporciona uma experiência um tanto desajeitada para o desenvolvedor.

IndexedDB é realmente um banco de dados orientado a objetos completo. Ele pode lidar com grandes quantidades de dados, modelados essencialmente como JSON. Ele oferece suporte a consultas, classificação e filtragem sofisticadas. Também está disponível em service workers como um mecanismo de persistência confiável entre reinicializações de thread e entre os threads principal e de trabalho.

Ao criar um armazenamento de objetos em IndexedDB, ele será associado ao domínio e durará até que o usuário o exclua. Ele pode ser usado como um armazenamento de dados offline para lidar com funcionalidades offline em aplicativos da web progressivos, no estilo do Google Docs.

Para sentir o sabor do uso IndexedDBveja como você pode criar uma nova loja:


let db = null; // A handle for the DB instance

llet request = indexedDB.open("MyDB", 1); // Try to open the “MyDB” instance (async operation)
request.onupgradeneeded = function(event) { // onupgradeneeded is the event indicated the MyDB is either new or the schema has changed
  db = event.target.result; // set the DB handle to the result of the onupgradeneeded event
  if (!db.objectStoreNames.contains("myObjectStore")) { // Check for the existence of myObjectStore. If it doesn’t exist, create it in the next step
    let tasksObjectStore = db.createObjectStore("myObjectStore", { autoIncrement: true }); // create myObjectStore
  }
};

A chamada para request.onsuccess = function(event) { db = event.target.result; }; // onsuccess dispara quando o banco de dados é aberto com sucesso. Isso irá disparar sem onupgradeneeded disparar se o DB e Object loja já existe. Neste caso, salvamos o db referência:


request.onerror = function(event) { console.log("Error in db: " + event); }; // If an error occurs, onerror will fire

O de cima IndexedDB o código é simples – ele apenas abre ou cria um banco de dados e um armazenamento de objetos – mas o código dá uma sensação de IndexedDBé a natureza assíncrona.

API de cache do service worker

Os service workers incluem um mecanismo especializado de armazenamento de dados chamado esconderijo. O cache facilita interceptar solicitações, salvar respostas e modificá-las, se necessário. Ele foi projetado principalmente para armazenar respostas em cache (como o nome indica) para uso offline ou para otimizar tempos de resposta. Isso é algo como um cache de proxy personalizável no navegador que funciona de forma transparente do ponto de vista do thread principal.

Aqui está uma olhada no armazenamento em cache de uma resposta usando uma estratégia de cache primeiro, em que você tenta obter a resposta primeiro do cache e, em seguida, faz fallback para a rede (salvando a resposta no cache):


self.addEventListener('fetch', (event) => {
  const request = event.request;
  const url = new URL(request.url);
  // Try serving assets from cache first
  event.respondWith(
    caches.match(request)
      .then((cachedResponse) => {
        // If found in cache, return the cached response
        if (cachedResponse) {
          return cachedResponse;
        }
        // If not in cache, fetch from network
        return fetch(request)
          .then((response) => {
            // Clone the response for potential caching
            const responseClone = response.clone();
            // Cache the new response for future requests
            caches.open('my-cache')
              .then((cache) => {
                cache.put(request, responseClone);
              });
            return response;
          });
      })
  );
});

Isso oferece uma abordagem altamente personalizável porque você tem acesso total aos objetos de solicitação e resposta.

Conclusão

Vimos as opções comumente usadas para persistir dados no navegador de vários perfis. Ao decidir qual usar, um algoritmo útil é: Qual é a opção mais simples que atende às minhas necessidades? Outra preocupação é a segurança, principalmente com cookies.

Outras possibilidades interessantes estão surgindo com o uso do WebAssembly para armazenamento persistente. A capacidade do Wasm de rodar nativamente no dispositivo pode aumentar o desempenho. Veremos como usar o Wasm para persistência de dados outro dia.