Guia de armazenamento em cache imperativo

Andrew Guan
Andrew Guan
Demián Renzulli
Demián Renzulli

Alguns sites podem precisar se comunicar com o service worker sem a necessidade informada sobre o resultado. Veja alguns exemplos:

  • Uma página envia ao service worker uma lista de URLs para pré-busca, para que, quando o usuário clicar em um que os sub-recursos do documento ou da página já estejam disponíveis no cache, tornando as a navegação com muito mais rapidez.
  • A página solicita que o service worker recupere e armazene em cache um conjunto de matérias importantes para que sejam disponíveis para fins off-line.

Delegar esses tipos de tarefas não críticas ao service worker tem o benefício de liberar o linha de execução principal para lidar melhor com tarefas mais urgentes, como responder às interações do usuário.

Diagrama de uma página que solicita recursos a serem armazenados em cache para um service worker.

Neste guia, exploraremos como implementar uma técnica de comunicação unidirecional da página para o service worker usando APIs de navegador padrão e a biblioteca Workbox. Chamaremos esses tipos de casos de uso de armazenamento em cache imperativo.

Caso de produção

O 1-800-Flowers.com implementou o armazenamento em cache imperativo (pré-busca) com service workers via postMessage() para pré-buscar o principais itens nas páginas de categorias para acelerar a navegação subsequente às páginas de detalhes do produto.

Logotipo da 1-800 Flowers.

A equipe usa uma abordagem mista para decidir quais itens serão pré-buscados:

  • No tempo de carregamento da página, ele pede ao servicer worker que recupere os dados JSON dos nove itens principais e adicione os objetos de resposta resultantes ao cache.
  • Para os itens restantes, ele ouve o mouseover evento, de modo que, quando um usuário mover o cursor sobre um item, poderá acionar uma busca do recurso sob demanda.

Eles usam a API Cache para armazenar dados JSON. respostas:

Logotipo da 1-800 Flowers.
Fazendo uma pré-busca de dados JSON do produto nas páginas de informações do produto em 1-800Flowers.com.

Quando o usuário clica em um item, os dados JSON associados a ele podem ser extraídos do cache, sem a necessidade de acessar a rede, tornando a navegação mais rápida.

Como usar o Workbox

O Workbox oferece uma maneira fácil de enviar mensagens para Um service worker, pelo pacote workbox-window, um conjunto de módulos que são executados no contexto da janela. Eles são um complemento para os outros pacotes de caixa de trabalho executados no service worker.

Para comunicar a página com o service worker, primeiro obtenha uma referência do objeto Workbox para o service worker registrado:

const wb = new Workbox('/sw.js');
wb.register();

Então, você pode enviar a mensagem diretamente de forma declarativa, sem o incômodo de obter o verificar a ativação ou pensar sobre a API de comunicação subjacente:

wb.messageSW({"type": "PREFETCH", "payload": {"urls": ["/data1.json", "data2.json"]}}); });

O service worker implementa um gerenciador message para ouvir essas mensagens. Ele pode, opcionalmente, retornar uma resposta, mas, em casos como esses, é não é necessário:

self.addEventListener('message', (event) => {
  if (event.data && event.data.type === 'PREFETCH') {
    // do something
  }
});

Como usar as APIs do navegador

Se a biblioteca Workbox não for suficiente para suas necessidades, confira como implementar a janela ao serviço comunicação do worker usando APIs de navegador.

A API postMessage pode ser usado para estabelecer um mecanismo de comunicação unidirecional da página com o service worker.

A página chama postMessage() no interface do service worker:

navigator.serviceWorker.controller.postMessage({
  type: 'MSG_ID',
  payload: 'some data to perform the task',
});

O service worker implementa um gerenciador message para ouvir essas mensagens.

self.addEventListener('message', (event) => {
  if (event.data && event.data.type === MSG_ID) {
    // do something
  }
});

O atributo {type : 'MSG_ID'} não é absolutamente obrigatório, mas é uma maneira de permitir que a página enviar diferentes tipos de instruções para o service worker (ou seja, "para pré-busca" vs. "para limpar armazenamento"). O service worker pode se ramificar em diferentes caminhos de execução com base nessa flag.

Se a operação tiver sido bem-sucedida, o usuário poderá aproveitar os benefícios, mas, se não tiver, o fluxo principal do usuário não será alterado. Por exemplo, quando 1-800-Flowers.com tenta pré-armazenar em cache, a página não precisa saber se o service worker foi bem-sucedido. Se tiver, o usuário desfrutará de uma navegação mais rápida. Caso contrário, ainda será necessário acessar a nova página. Só vai demorar um pouco mais.

Um exemplo simples de pré-busca

Uma das aplicações mais comuns do armazenamento em cache imperativo é a pré-busca, que significa buscar recursos de um determinado URL, antes que o usuário vá até ele, a fim de acelerar a navegação.

Há maneiras diferentes de implementar a pré-busca em sites:

Para cenários de pré-busca relativamente simples, como pré-busca de documentos ou recursos específicos (JS, CSS etc.), essas técnicas são a melhor abordagem.

Se uma lógica adicional for necessária, por exemplo, analisar o recurso de pré-busca (um arquivo ou página JSON) em para buscar seus URLs internos, é mais apropriado delegar essa tarefa inteiramente ao service worker.

Delegar esses tipos de operações ao service worker tem as seguintes vantagens:

  • Descarregando o trabalho pesado de busca e processamento pós-busca (que serão apresentados mais tarde) para um thread secundário. Ao fazer isso, ela libera a linha de execução principal para lidar com tarefas tarefas como responder às interações do usuário.
  • Permitir que vários clientes (por exemplo, guias) reutilizem uma funcionalidade comum e até mesmo chamarem o sem bloquear a linha de execução principal.

Fazer uma pré-busca das páginas de detalhes do produto

Primeiro, use postMessage() em interface do service worker e passe uma matriz de URLs para armazenar em cache:

navigator.serviceWorker.controller.postMessage({
  type: 'PREFETCH',
  payload: {
    urls: [
      'www.exmaple.com/apis/data_1.json',
      'www.exmaple.com/apis/data_2.json',
    ],
  },
});

No service worker, implemente um gerenciador message para interceptar e processar mensagens enviadas por qualquer guia ativa:

addEventListener('message', (event) => {
  let data = event.data;
  if (data && data.type === 'PREFETCH') {
    let urls = data.payload.urls;
    for (let i in urls) {
      fetchAsync(urls[i]);
    }
  }
});

No código anterior, introduzimos uma pequena função auxiliar chamada fetchAsync() para iterar na matriz de URLs e emitirá uma solicitação de busca para cada um deles:

async function fetchAsync(url) {
  // await response of fetch call
  let prefetched = await fetch(url);
  // (optionally) cache resources in the service worker storage
}

Quando a resposta é recebida, você pode confiar nos cabeçalhos de armazenamento em cache do recurso. Em muitos casos, no entanto, como nas páginas de detalhes do produto, os recursos não são armazenados em cache, ou seja, cabeçalho Cache-control de no-cache). Em casos como esses, você pode substituir esse comportamento, armazenar o recurso buscado no cache do service worker. Isso tem a vantagem adicional de permitir que seja exibido em cenários off-line.

Além dos dados JSON

Quando os dados JSON são buscados em um endpoint do servidor, eles geralmente contêm outros URLs que também são que vale a pré-busca, como uma imagem ou outros dados de endpoint associados a esse dados.

Digamos que, em nosso exemplo, os dados JSON retornados sejam as informações de um site de compras de supermercado:

{
  "productName": "banana",
  "productPic": "https://cdn.example.com/product_images/banana.jpeg",
  "unitPrice": "1.99"
 }

Modifique o código fetchAsync() para iterar a lista de produtos e armazenar em cache a imagem principal para para cada um deles:

async function fetchAsync(url, postProcess) {
  // await response of fetch call
  let prefetched = await fetch(url);

  //(optionally) cache resource in the service worker cache

  // carry out the post fetch process if supplied
  if (postProcess) {
    await postProcess(prefetched);
  }
}

async function postProcess(prefetched) {
  let productJson = await prefetched.json();
  if (productJson && productJson.product_pic) {
    fetchAsync(productJson.product_pic);
  }
}

Você pode adicionar alguma manipulação de exceção nesse código para situações como 404s. Mas a beleza de usar um service worker para fazer a pré-busca é que ele pode falhar sem muito consequência para a página e a linha de execução principal. Você também pode ter uma lógica mais elaborada no pós-processamento do conteúdo pré-buscado, tornando-o mais flexível e desacoplado dos dados processamento. O céu é o limite.

Conclusão

Neste artigo, abordamos um caso de uso comum de comunicação unidirecional entre página e serviço. worker: armazenamento em cache imperativo. Os exemplos discutidos servem apenas para demonstrar uma forma de usando esse padrão e a mesma abordagem também pode ser aplicada a outros casos de uso, por exemplo, armazenamento em cache dos principais artigos sob demanda para consumo off-line, adição aos favoritos, entre outros.

Para mais padrões de comunicação de service worker e páginas, confira:

  • Transmitir atualizações: chamar a página do service worker para informar sobre atualizações importantes (por exemplo, uma nova versão do webapp está disponível).
  • Comunicação bidirecional: delegar uma tarefa a um service worker (por exemplo, um download pesado) e manter a página informada sobre o progresso.