API Cache: um guia rápido

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

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

A API Cache foi criada para permitir que os service workers armazenem em cache solicitações de rede para que possam 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.

Onde ele está disponível?

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

const cacheAvailable = 'caches' in self;

Compatibilidade com navegadores

  • 40
  • 16
  • 41
  • 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, representando solicitações e respostas HTTP, respectivamente. No entanto, as solicitações e respostas podem conter qualquer tipo de dados que possa ser transferido por HTTP.

Quantos dados podem ser armazenados?

Resumindo, muitos, pelo menos algumas centenas de megabytes e possivelmente centenas de gigabytes ou mais. As implementações do navegador variam, mas a quantidade de armazenamento disponível geralmente é baseada na quantidade de 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...

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), faz uma solicitação à 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 de origem cruzada 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, temos o cache.addAll(). Ele funciona de maneira semelhante a add(), mas usa uma matriz de objetos ou URLs Request (strings). Isso funciona de maneira semelhante a chamar cache.add para cada solicitação individual, exceto pelo fato de que Promise é rejeitado se nenhuma solicitação única não for 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. Isso usa as mesmas regras de correspondência descritas na seção sobre retrieving.

cache.put

Por fim, há cache.put(), que permite armazenar uma resposta da rede ou criar e armazenar seu próprio Response. São necessários 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 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 que add() ou addAll() e permite armazenar respostas que não são CORS ou outras respostas em que o código de status da resposta não está no intervalo 200. As respostas anteriores da mesma solicitação serão substituídas.

Como criar objetos de solicitação

Crie o objeto Request usando um URL do item que está sendo armazenado:

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

Como trabalhar com objetos de resposta

O construtor do objeto 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 uma Response definindo o cabeçalho apropriado.

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

Se você recuperou um Response e quer acessar o corpo dele, há vários métodos auxiliares que podem ser usados. Cada um retorna um Promise que é resolvido com um valor de 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 tipo. 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, em seguida, tenta analisá-lo como JSON. Retorna o objeto resultante ou gera uma TypeError se a string não pode 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 um 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 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 a converterá em um Request chamando new Request(request). Ela vai retornar um Promise, que será resolvido como Response se uma entrada correspondente for encontrada. Caso contrário, retornará undefined.

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

É possível ignorar algumas ou todas essas coisas passando 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 todos os caches de uma só 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 entradas correspondentes a um objeto Response. No entanto, é possível implementar sua própria pesquisa usando filtragem ou criando um índice.

Filtragem

Uma maneira de implementar sua própria pesquisa é iterar todas as entradas e filtrar as que você quer. Digamos que você queira encontrar todos os itens com URLs que terminam com .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, é possível usar qualquer propriedade dos objetos Request e Response para filtrar as entradas. Esse processo 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 grandes números de entradas.

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

Excluir um item

Para excluir um item de um cache:

cache.delete(request);

A solicitação pode ser 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 do 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 para true se o cache existiu e foi excluído, ou false se não tiver.

Até logo!

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