Continuando a série sobre PWA tivemos uma introdução sobre Service Worker. Vimos que o service worker permite o suporte a aplicações offline mas também podemos controlar as requisições feitas pelo o navegador isso nos dá a habilidade de pensar em diferentes estratégias de cache para a nossa aplicação e esse será o tema do nosso post.

Desenvolvendo uma aplicação web alguns dados dentro da nossa solução tem uma periodicidade maior ou menor de atualização, por exemplo a logo de nosso site é tipo de dado que pode passar anos sem alteração, já no caso de uma sessão de últimas notícias podem ter uma periodicidade de minutos. Com esse dois exemplo podemos ver situações distintas, assim com a partir desses exemplos vamos abordar as estratégias mais comum de cache.

Cache only

Nesse caso todos as requisições serão direcionadas para o cache.

  1. O service worker recebe a requisição
  2. Consulta se o conteúdo se encontra no cache
  3. Retorna o conteúdo requisitado para o usuário

Se não encontrado no cache a requisição irá falhar. Mas esse pattern assume que os conteúdos serão armazenados no cache durante a instalação do service worker.

Casos recomendados: esse padrão é ideal para assets que raramente são atualizados, por exemplo, logos, ícones de redes sociais e estilos. Mas isso não significa que eles nunca serão alterados, esse controle será feito pela versão do seu service worker.

Código:

self.addEventListener('fetch', event => {
  // todos os arquivos serão servidos pelo cache 
  event.respondWith(caches.match(event.request));
})

Cache first

Parecida com a estratégia anterior, mas com uma diferença, caso não ache o arquivo no cache ele irá realizar uma requisição na rede como podemos ver no código a seguir:

self.addEventListener('fetch', event => {
   
  event.respondWith(
    caches.match(event.request).then(response => {
      return response || fetch(event.request);
    })
  );
});

Network only

  1. O service worker irá analisar a requisição
  2. Irá requisitar na rede os arquivos requisitados
  3. O conteúdo requisitado é enviado para o usuário

Neste caso se a requisição a rede falhar não retornará nada.

Casos recomendados: Esse útil para requisições exemplo pings no servidor para análise de tráfego ou estatísticas.

Network first

Nessa situação primeiramente fazemos o request para a rede, caso a requisição falhe procuramos o arquivo no cache. Problema nessa requisição que perdemos muito tempo esperando um retorno de uma falha na requisição.

Casos recomendados Trabalhamos com essa estratégia quando temos a prioridade dos dados recentes. Ideal para áreas que tem atualizações constantes.

Código:

self.addEventListener('fetch', event => {
  event.respondWith(
    fetch(event.request).catch(() => {
      return caches.match(event.request);
    })
  );
});

Cache, then network

Essa estratégia carrega os dados imediatamente do cache enquanto verifica na rede se o conteúdo sofre alguma alteração. No momento que receber o retorno das informações da redes, o conteúdo será verificado caso precisa será atualizado.

Temos o benéfico de exibir o conteúdo imediatamente mas sempre fazemos duas requisições precisando ou não da informação. Por que nesse caso só sabemos se o conteúdo foi alterado se requisitamos a informação na rede, se nada mudou, fazemos uma requisição em vão. Outro desafio desse padrão é desenvolver uma interface que não pareça estranho a atualização do conteúdo.

Casos recomendados: Quando priorizamos a performance mas temos uma frequência constante de updates do conteúdo, aplicações com timeline de e game leader board.

Código (sw.js)

self.addEventListener('fetch', event => {
   event.respondWith(
     caches.open('mysite-dynamic').then(cache => {
        return fetch(event.request).then(response => {
          cache.put(event.request, response.clone());
          return response;
        })
     })
   );
 });

Código(main.js)

let networkDataReceived = false;
startSpinner()
const networkUpdate = fetch('/data.json').then(response => {
  if(!response) throw Error('no data');
  return response.json();
}).then(data => {
  if(!networkDataReceived);
  updatePage(data);
}).catch(() => {
  return networkUpdate;
}).catch(showErrorMessage).then(stopSpinner);

Stale while revalidate

Utilizamos essa técnica quando não temos prioridade do conteúdo mais recente mas queremos fazer atualizações periódicas. Essa estratégia é parecida com a cache first, mas com uma diferença os dados mais recentes não serão atualizados imediatamente, o cache será atualizado e a informação será exibida quando o usuário recarregar a página.

Casos recomendados: Logos, imagens do perfil do usuário.

Código

self.addEventListener('fetch', event => {
   event.respondWith(
     caches.open('mysite-dynamic').then(cache => {
        return cache.match(event.request).then(response => {
         const fetchPromise = fetch(event.request)
          .then(networkResponse => {
           cache.put(event.request, networkResponse.clone());
           return networkResponse;
          });
          return response || fetchPromise;
        })
     })
   );
 });

Generic fallback

Quando o usuário usuário não achar o conteúdo requisitado na rede ou no cache retomamos um conteúdo genérico. Por exemplo, quando não conseguimos encontrar uma imagem específica como um avatar, para não prejudicar nossa interface retornamos um avatar genérico, por exemplo, uma ilustração. Esse padrão pode ser combinado com as estratégias anteriores.

Código

self.addEventListener('fetch', event => {  
  event.respondWith(
    caches.match(event.request).then(response => {
     return response || fetch(event.request);
    }).catch(() => {
      return caches.match('/offline.html');
    })
  );
});

Esse são as principais estratégias de cache, além delas podemos combinar estratégias de cache, mas para esse post vamos abordar os mais populares nos próximos posts iremos abordar uma ferramenta que irá facilitar nossa vida: Workbox, mas isso é assunto para um novo post.

Caso tenha alguma dúvida deixe um comentário e até o próximo post.

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *