Estratégias para armazenamento em cache do service worker

Até agora, havia apenas menções e pequenos snippets de código dos Interface Cache. Para usar os service workers de forma eficaz, é necessário adotar uma ou mais estratégias de armazenamento em cache, que requer um pouco de familiaridade com a interface Cache.

Uma estratégia de armazenamento em cache é uma interação entre o evento fetch de um service worker e a interface Cache. Depende da forma como uma estratégia de armazenamento em cache é criada. por exemplo, pode ser preferível tratar solicitações de ativos estáticos de maneira diferente de documentos, Isso afeta a composição da estratégia de armazenamento em cache.

Antes de abordarmos as estratégias em si, vamos falar um pouco sobre o que a interface Cache não é e o que ela é. e um resumo rápido de alguns dos métodos oferecidos para gerenciar caches de service workers.

A interface Cache versus o cache HTTP

Se você nunca trabalhou com a interface Cache, pode ser tentador pensar nisso como a mesma coisa: ou ao menos relacionada ao cache HTTP. Esse não é o caso.

  • A interface Cache é um mecanismo de armazenamento em cache totalmente separado do cache HTTP.
  • O que quer Cache-Control A configuração usada para influenciar o cache HTTP não influencia quais recursos são armazenados na interface Cache.

Pode ser útil pensar nos caches do navegador como camadas. O cache HTTP é de baixo nível, orientado por pares de chave-valor com diretivas expressas em cabeçalhos HTTP.

A interface Cache, por outro lado, é um cache de alto nível orientado por uma API JavaScript. Isso oferece mais flexibilidade do que ao usar pares de chave-valor HTTP relativamente simples. e é metade do que torna possíveis estratégias de armazenamento em cache. Alguns métodos de API importantes relacionados a caches de service workers são:

  • CacheStorage.open para criar uma nova instância Cache.
  • Cache.add e Cache.put para armazenar respostas de rede em um cache de service worker.
  • Cache.match para localizar uma resposta armazenada em cache em uma instância do Cache.
  • Cache.delete para remover uma resposta armazenada em cache de uma instância do Cache.

Esses são apenas alguns exemplos. Existem outros métodos úteis, mas esses são os conceitos básicos que você verá mais adiante neste guia.

O modesto evento fetch

A outra metade de uma estratégia de armazenamento em cache é evento fetch. Até agora nesta documentação, você ouviu um pouco sobre "interceptação de solicitações de rede", e o evento fetch dentro de um service worker é onde isso acontece:

// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';

self.addEventListener('install', (event) => {
  event.waitUntil(caches.open(cacheName));
});

self.addEventListener('fetch', async (event) => {
  // Is this a request for an image?
  if (event.request.destination === 'image') {
    // Open the cache
    event.respondWith(caches.open(cacheName).then((cache) => {
      // Respond with the image from the cache or from the network
      return cache.match(event.request).then((cachedResponse) => {
        return cachedResponse || fetch(event.request.url).then((fetchedResponse) => {
          // Add the network response to the cache for future visits.
          // Note: we need to make a copy of the response to save it in
          // the cache and use the original as the request response.
          cache.put(event.request, fetchedResponse.clone());

          // Return the network response
          return fetchedResponse;
        });
      });
    }));
  } else {
    return;
  }
});

Este é um exemplo de brinquedo... e um que você possa ver em ação, mas e oferece uma visão geral do que os service workers podem fazer. O código acima faz o seguinte:

  1. Inspecione a propriedade destination da solicitação para ver se é uma solicitação de imagem.
  2. Se a imagem estiver no cache do service worker, exiba-a de lá. Caso contrário, busque a imagem na rede armazenar a resposta no cache e retornar a resposta da rede.
  3. Todas as outras solicitações são passadas pelo service worker sem interação com o cache.

O objeto event de uma busca contém um Propriedade do request algumas informações úteis para ajudar você a identificar o tipo de cada solicitação:

  • url, que é o URL da solicitação de rede que está sendo processada pelo evento fetch.
  • method, que é o método de solicitação (por exemplo, GET ou POST).
  • mode, que descreve o modo da solicitação. Um valor de 'navigate' é frequentemente usado para distinguir solicitações de documentos HTML de outras solicitações.
  • destination, que descreve o tipo de conteúdo solicitado de forma a evitar o uso da extensão de arquivo do recurso solicitado.

Mais uma vez, "asynchrony" é o nome do jogo. Você lembra que o evento install oferece uma event.waitUntil que usa uma promessa e aguarda a resolução antes de prosseguir para a ativação. O evento fetch oferece uma Método event.respondWith que pode ser usada para retornar o resultado de uma solicitação Solicitação do fetch ou uma resposta retornada pela interface Cache método match.

Estratégias de armazenamento em cache

Agora que você já conhece um pouco as instâncias Cache e o manipulador de eventos fetch, está tudo pronto para você conhecer algumas estratégias de armazenamento em cache dos service workers. As possibilidades são praticamente infinitas, este guia seguirá com as estratégias fornecidas com o Workbox, para ter uma noção do que acontece nos componentes internos do Workbox.

Somente cache

Mostra o fluxo da página para o service worker e para o cache.

Vamos começar com uma estratégia simples de armazenamento em cache chamada "Somente cache". Quando o service worker está no controle da página, solicitações correspondentes irão apenas para o cache. Isso significa que todos os recursos em cache precisarão ser pré-armazenados em cache para que estejam disponíveis para que o padrão funcione, e que esses recursos nunca serão atualizados no cache até que o service worker seja atualizado.

// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';

// Assets to precache
const precachedAssets = [
  '/possum1.jpg',
  '/possum2.jpg',
  '/possum3.jpg',
  '/possum4.jpg'
];

self.addEventListener('install', (event) => {
  // Precache assets on install
  event.waitUntil(caches.open(cacheName).then((cache) => {
    return cache.addAll(precachedAssets);
  }));
});

self.addEventListener('fetch', (event) => {
  // Is this one of our precached assets?
  const url = new URL(event.request.url);
  const isPrecachedRequest = precachedAssets.includes(url.pathname);

  if (isPrecachedRequest) {
    // Grab the precached asset from the cache
    event.respondWith(caches.open(cacheName).then((cache) => {
      return cache.match(event.request.url);
    }));
  } else {
    // Go to the network
    return;
  }
});

Acima, uma matriz de recursos é pré-armazenada em cache no momento da instalação. Quando o service worker gerencia buscas, verificamos se o URL da solicitação processado pelo evento fetch está na matriz de recursos pré-armazenados em cache. Nesse caso, pegamos o recurso do cache e pulamos a rede. Outras solicitações passam pela rede, e somente a rede. Para ver essa estratégia em ação, confira esta demonstração com o console aberto.

Somente rede

Mostra o fluxo da página para o service worker e para a rede.

O oposto de "Somente cache" é "Somente rede", em que uma solicitação é passada por um service worker para a rede sem nenhuma interação com o cache do service worker. Essa é uma boa estratégia para garantir a atualização do conteúdo (pense na marcação), mas a desvantagem é que ela nunca vai funcionar quando o usuário estiver off-line.

Garantir que uma solicitação seja transmitida à rede significa apenas que você não vai chamar event.respondWith para uma solicitação correspondente. Se você quiser ser explícito, Você pode inserir um return; vazio no callback do evento fetch para solicitações que você quer transmitir para a rede. Isso é o que acontece na configuração "Somente cache" de estratégia para solicitações que não são pré-armazenadas em cache.

Cache primeiro, com retorno à rede

Mostra o fluxo da página para o service worker, para o cache e, em seguida, para a rede, se não estiver no cache.

É nessa estratégia que as coisas se envolvem um pouco mais. Para solicitações correspondentes, o processo é o seguinte:

  1. A solicitação atinge o cache. Se o recurso estiver no cache, veicule-o de lá.
  2. Se a solicitação não estiver no cache, acesse a rede.
  3. Quando a solicitação de rede terminar, adicione-a ao cache e depois retornar a resposta da rede.

Aqui está um exemplo dessa estratégia, que você pode testar em uma demonstração ao vivo:

// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';

self.addEventListener('fetch', (event) => {
  // Check if this is a request for an image
  if (event.request.destination === 'image') {
    event.respondWith(caches.open(cacheName).then((cache) => {
      // Go to the cache first
      return cache.match(event.request.url).then((cachedResponse) => {
        // Return a cached response if we have one
        if (cachedResponse) {
          return cachedResponse;
        }

        // Otherwise, hit the network
        return fetch(event.request).then((fetchedResponse) => {
          // Add the network response to the cache for later visits
          cache.put(event.request, fetchedResponse.clone());

          // Return the network response
          return fetchedResponse;
        });
      });
    }));
  } else {
    return;
  }
});

Embora este exemplo aborde apenas imagens, essa é uma ótima estratégia para aplicar a todos os recursos estáticos (como CSS, JavaScript, imagens e fontes), especialmente os com controle de versão com hash. Ele oferece um aumento de velocidade para ativos imutáveis, evitando todas as verificações de atualização de conteúdo com o servidor que o cache HTTP pode iniciar. Mais importante, todos os recursos armazenados em cache estarão disponíveis off-line.

Rede em primeiro lugar, com retorno ao cache

Mostra o fluxo da página para o service worker e para a rede e, em seguida, para o cache se a rede não estiver disponível.

Se você virasse "Cache primeiro, depois rede" de cabeça, você acabará com a mensagem "Rede em primeiro lugar, segundo cache" estratégia, que é o que parece:

  1. Primeiro, você acessa a rede para fazer uma solicitação e coloca a resposta no cache.
  2. Se ficar off-line posteriormente, você retorna para a versão mais recente dessa resposta no cache.

Essa estratégia é ótima para solicitações HTML ou de API quando: enquanto está on-line, você quer a versão mais recente de um recurso, mas quiser dar acesso off-line à versão mais recente disponível. Veja como isso pode ficar quando aplicado às solicitações de HTML:

// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';

self.addEventListener('fetch', (event) => {
  // Check if this is a navigation request
  if (event.request.mode === 'navigate') {
    // Open the cache
    event.respondWith(caches.open(cacheName).then((cache) => {
      // Go to the network first
      return fetch(event.request.url).then((fetchedResponse) => {
        cache.put(event.request, fetchedResponse.clone());

        return fetchedResponse;
      }).catch(() => {
        // If the network is unavailable, get
        return cache.match(event.request.url);
      });
    }));
  } else {
    return;
  }
});

Teste isso em uma demonstração. Primeiro, acesse a página. Talvez seja necessário recarregar antes que a resposta HTML seja colocada no cache. Em seguida, nas ferramentas para desenvolvedores, simular uma conexão off-line, e atualize novamente. A última versão disponível será veiculada instantaneamente a partir do cache.

Em situações em que o recurso off-line é importante, mas você precisa equilibrar essa capacidade com o acesso à versão mais recente de um pouco de marcação ou dados da API, "Rede primeiro, segundo cache" é uma estratégia sólida que atinge essa meta.

obsoleto durante a revalidação

Mostra o fluxo da página para o service worker, para o cache e, em seguida, da rede para o cache.

Das estratégias que abordamos até agora, "Stale-ao revalidar" é o mais complexo. De certa forma, é semelhante às duas últimas estratégias, mas o procedimento prioriza a velocidade de acesso a um recurso, além de mantê-lo atualizado em segundo plano. Essa estratégia funciona assim:

  1. Na primeira solicitação de um recurso, busque-o na rede colocá-la no cache e retornar a resposta da rede.
  2. Em solicitações subsequentes, veicule o recurso a partir do cache primeiro e depois "em segundo plano", solicitá-lo novamente da rede e atualizar a entrada de cache do recurso.
  3. Para solicitações posteriores, você receberá a última versão buscada na rede que foi colocada no cache na etapa anterior.

Essa é uma excelente estratégia para coisas importantes que precisam ser atualizadas, mas não essenciais. Pense em coisas como avatares para um site de mídia social. Eles são atualizados quando os usuários começam a usar o recurso mas a versão mais recente não é estritamente necessária em todas as solicitações.

// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';

self.addEventListener('fetch', (event) => {
  if (event.request.destination === 'image') {
    event.respondWith(caches.open(cacheName).then((cache) => {
      return cache.match(event.request).then((cachedResponse) => {
        const fetchedResponse = fetch(event.request).then((networkResponse) => {
          cache.put(event.request, networkResponse.clone());

          return networkResponse;
        });

        return cachedResponse || fetchedResponse;
      });
    }));
  } else {
    return;
  }
});

Você pode ver isso em ação mais uma demonstração ao vivo, especialmente se você prestar atenção à guia "Rede" nas ferramentas para desenvolvedores do navegador, e o visualizador de CacheStorage, se as ferramentas para desenvolvedores do navegador tiverem uma ferramenta desse tipo.

Vamos para o Workbox!

Este documento encerra nossa revisão da API de service workers, assim como as APIs relacionadas, o que significa que você aprendeu o suficiente sobre como usar os service workers diretamente para começar a mexer no Workbox.