API Cache: um guia rápido

Saiba como usar a API Cache para disponibilizar os dados do aplicativo off-line.

A API Cache é um sistema para armazenar e extrair solicitações de rede e as respostas correspondentes. Elas podem ser solicitações e respostas regulares criadas durante a execução do aplicativo ou criada apenas para armazenar dados para uso posterior.

A API Cache foi criada para permitir que os service workers armazenem em cache as solicitações de rede para fornecer respostas rápidas, independentemente da velocidade ou disponibilidade da rede. No entanto, a API também pode ser usada como um mecanismo de armazenamento geral.

A API Cache está disponível em todos os navegadores modernos. Ela é exposta pela propriedade global caches. Assim, você pode testar a presença da API com uma detecção de recursos simples:

const cacheAvailable = 'caches' in self;

Compatibilidade com navegadores

  • Chrome: 40.
  • Edge: 16.
  • Firefox: 41.
  • Safari: 11.1.

Origem

A API Cache pode ser acessada de uma janela, um iframe, um worker ou um service worker.

O que pode ser armazenado

Os caches armazenam apenas pares de objetos Request e Response, que representam solicitações e respostas HTTP, respectivamente. No entanto, as solicitações e respostas podem conter qualquer tipo de dados que possam ser transferidos por HTTP.

Quanto pode ser armazenado?

Em resumo, muito, pelo menos algumas centenas de megabytes e, potencialmente, centenas de gigabytes ou mais. As implementações do navegador variam, mas a quantidade de armazenamento disponível geralmente é baseada no armazenamento disponível no dispositivo.

Como criar e abrir um cache

Para abrir um cache, use o método caches.open(name), transmitindo o nome do cache como o único parâmetro. Se o cache nomeado não existir, ele será criado. Esse método retorna um Promise que é resolvido com o objeto Cache.

const cache = await caches.open('my-cache');
// do something with cache...

Como adicionar a um cache

Há três maneiras de adicionar um item a um cache: add, addAll e put. Todos os três métodos retornam um Promise.

cache.add

Primeiro, há cache.add(). Ele usa um parâmetro, um Request ou um URL (string). Ele faz uma solicitação para a rede e armazena a resposta no cache. Se a busca falhar ou se o código de status da resposta não estiver no intervalo 200, nada será armazenado e a Promise será rejeitada. As solicitações entre origens que não estão no modo CORS não podem ser armazenadas porque retornam um status de 0. Essas solicitações só podem ser armazenadas com put.

// Retreive data.json from the server and store the response.
cache.add(new Request('/data.json'));

// Retreive data.json from the server and store the response.
cache.add('/data.json');

cache.addAll

Em seguida, notícias de cache.addAll(). Ele funciona de maneira semelhante a add(), mas recebe uma matriz de objetos Request ou URLs (strings). Isso funciona de maneira semelhante a chamada de cache.add para cada solicitação individual, exceto que o Promise rejeita se uma única solicitação não estiver armazenada em cache.

const urls = ['/weather/today.json', '/weather/tomorrow.json'];
cache.addAll(urls);

Em cada um desses casos, uma nova entrada substitui qualquer entrada existente correspondente. Ele usa as mesmas regras de correspondência descritas na seção sobre como recuperar.

cache.put

Por fim, há o cache.put(), que permite armazenar uma resposta da rede ou criar e armazenar o próprio Response. Ele usa dois parâmetros. O primeiro pode ser um objeto Request ou um URL (string). O segundo precisa ser um Response, da rede ou gerado pelo seu código.

// Retrieve data.json from the server and store the response.
cache.put('/data.json');

// Create a new entry for test.json and store the newly created response.
cache.put('/test.json', new Response('{"foo": "bar"}'));

// Retrieve data.json from the 3rd party site and store the response.
cache.put('https://example.com/data.json');

O método put() é mais permissivo do que add() ou addAll() e permite armazenar respostas que não são CORS ou outras respostas em que o código de status não está no intervalo 200. Ele vai substituir todas as respostas anteriores para a mesma solicitação.

Como criar objetos de solicitação

Crie o objeto Request usando um URL para a coisa armazenada:

const request = new Request('/my-data-store/item-id');

Como trabalhar com objetos de resposta

O construtor de objetos Response aceita muitos tipos de dados, incluindo Blobs, ArrayBuffers, objetos FormData e strings.

const imageBlob = new Blob([data], {type: 'image/jpeg'});
const imageResponse = new Response(imageBlob);
const stringResponse = new Response('Hello world');

É possível definir o tipo MIME de um Response definindo o cabeçalho apropriado.

  const options = {
    headers: {
      'Content-Type': 'application/json'
    }
  }
  const jsonResponse = new Response('{}', options);

Se você extrair um Response e quiser acessar o corpo dele, use vários métodos auxiliares. Cada um retorna um Promise que é resolvido com um valor de um tipo diferente.

Método Descrição
arrayBuffer Retorna um ArrayBuffer contendo o corpo, serializado em bytes.
blob Retorna um Blob. Se o Response foi criado com um Blob, esse novo Blob tem o mesmo type. Caso contrário, o Content-Type do Response será usado.
text Interpreta os bytes do corpo como uma string codificada em UTF-8.
json Interpreta os bytes do corpo como uma string codificada em UTF-8 e tenta analisá-la como JSON. Retorna o objeto resultante ou gera uma TypeError se a string não puder ser analisada como JSON.
formData Interpreta os bytes do corpo como um formulário HTML, codificado como multipart/form-data ou application/x-www-form-urlencoded. Retorna um objeto FormData ou gera uma TypeError se os dados não puderem ser analisados.
body Retorna um ReadableStream para os dados do corpo.

Por exemplo:

const response = new Response('Hello world');
const buffer = await response.arrayBuffer();
console.log(new Uint8Array(buffer));
// Uint8Array(11) [72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]

Como recuperar dados de um cache

Para encontrar um item em um cache, use o método match.

const response = await cache.match(request);
console.log(request, response);

Se request for uma string, o navegador vai convertê-la em uma Request chamando new Request(request). A função retorna um Promise que é resolvido como um Response se uma entrada correspondente for encontrada ou undefined, caso contrário.

Para determinar se dois Requests correspondem, o navegador usa mais do que apenas o URL. Duas solicitações são consideradas diferentes se tiverem strings de consulta, cabeçalhos Vary ou métodos HTTP (GET, POST, PUT etc.) diferentes.

É possível ignorar algumas ou todas essas coisas transmitindo um objeto de opções como um segundo parâmetro.

const options = {
  ignoreSearch: true,
  ignoreMethod: true,
  ignoreVary: true
};

const response = await cache.match(request, options);
// do something with the response

Se mais de uma solicitação em cache corresponder, a que foi criada primeiro será retornada. Se você quiser recuperar todas as respostas correspondentes, use cache.matchAll().

const options = {
  ignoreSearch: true,
  ignoreMethod: true,
  ignoreVary: true
};

const responses = await cache.matchAll(request, options);
console.log(`There are ${responses.length} matching responses.`);

Como atalho, você pode pesquisar em todos os caches de uma vez usando caches.match() em vez de chamar cache.match() para cada cache.

Pesquisando…

A API Cache não oferece uma maneira de pesquisar solicitações ou respostas, exceto para entradas correspondentes a um objeto Response. No entanto, é possível implementar sua própria pesquisa usando a filtragem ou criando um índice.

Filtragem

Uma maneira de implementar sua própria pesquisa é iterar sobre todas as entradas e filtrar para as que você quiser. Digamos que você queira encontrar todos os itens com URLs que terminam em .png.

async function findImages() {
  // Get a list of all of the caches for this origin
  const cacheNames = await caches.keys();
  const result = [];

  for (const name of cacheNames) {
    // Open the cache
    const cache = await caches.open(name);

    // Get a list of entries. Each item is a Request object
    for (const request of await cache.keys()) {
      // If the request URL matches, add the response to the result
      if (request.url.endsWith('.png')) {
        result.push(await cache.match(request));
      }
    }
  }

  return result;
}

Dessa forma, você pode usar qualquer propriedade dos objetos Request e Response para filtrar as entradas. Isso será lento se você pesquisar em grandes conjuntos de dados.

Como criar um índice

A outra maneira de implementar sua própria pesquisa é manter um índice separado de entradas que podem ser pesquisadas e armazenar o índice no IndexedDB. Como esse é o tipo de operação para o qual o IndexedDB foi projetado, ele tem um desempenho muito melhor com grande número de entradas.

Se você armazenar o URL do Request com as propriedades pesquisáveis, poderá recuperar facilmente a entrada de cache correta depois de fazer a pesquisa.

Como excluir um item

Para excluir um item de um cache:

cache.delete(request);

Em que a solicitação pode ser uma Request ou uma string de URL. Esse método também usa o mesmo objeto de opções que cache.match, o que permite excluir vários pares Request/Response para o mesmo URL.

cache.delete('/example/file.txt', {ignoreVary: true, ignoreSearch: true});

Excluir um cache

Para excluir um cache, chame caches.delete(name). Essa função retorna um Promise que é resolvido como true se o cache existir e for excluído ou false, caso contrário.

Obrigado

Agradecemos a Mat Scales, que escreveu a versão original deste artigo, que apareceu pela primeira vez no WebFundamentals.