Um dos principais aspectos dos Progressive Web Apps é a confiabilidade. Eles podem carregar recursos rapidamente, mantendo os usuários engajados e fornecendo feedback imediato, mesmo em condições de rede ruins. Como isso é possível? Graças ao evento fetch
do service worker.
O evento de busca
O evento fetch
permite interceptar todas as solicitações de rede feitas pelo PWA no escopo do service worker, tanto para solicitações de mesma origem quanto de origem cruzada. Além de solicitações de navegação e recursos, a busca de um service worker instalado permite que as visitas à página após o primeiro carregamento de um site sejam renderizadas sem chamadas de rede.
O manipulador fetch
recebe todas as solicitações de um app, incluindo URLs e cabeçalhos HTTP, e permite que o desenvolvedor do app decida como processá-las.
O service worker pode encaminhar uma solicitação para a rede, responder com uma resposta armazenada em cache anteriormente ou criar uma nova resposta. A escolha é sua. Veja um exemplo simples:
self.addEventListener("fetch", event => {
console.log(`URL requested: ${event.request.url}`);
});
Como responder a uma solicitação
Quando uma solicitação chega ao service worker, há duas coisas que você pode fazer: ignorar, o que permite que ela vá para a rede, ou responder. Responder a solicitações do service worker é como você pode escolher o que e como ele é retornado ao PWA, mesmo quando o usuário está off-line.
Para responder a uma solicitação recebida, chame event.respondWith()
em um manipulador de eventos fetch
, assim:
// fetch event handler in your service worker file
self.addEventListener("fetch", event => {
const response = .... // a response or a Promise of response
event.respondWith(response);
});
É necessário chamar respondWith()
de forma síncrona e retornar um objeto Response. Mas não é possível chamar respondWith()
depois que o manipulador de eventos de busca termina, como em uma chamada assíncrona. Se você precisar esperar a resposta completa, transmita uma promessa para respondWith()
que seja resolvida com uma resposta.
Como criar respostas
Graças à API Fetch, é possível criar respostas HTTP no código JavaScript, e essas respostas podem ser armazenadas em cache usando a API Cache Storage e retornadas como se viessem de um servidor da Web.
Para criar uma resposta, crie um objeto Response
, definindo o corpo e opções como status e cabeçalhos:
const simpleResponse = new Response("Body of the HTTP response");
const options = {
status: 200,
headers: {
'Content-type': 'text/html'
}
};
const htmlResponse = new Response("<b>HTML</b> content", options)
Responder do cache
Agora que você sabe como veicular respostas HTTP de um service worker, é hora de usar a interface Caching Storage para armazenar recursos no dispositivo.
Use a API Cache Storage para verificar se a solicitação recebida do PWA está disponível no cache e, se estiver, responda a respondWith()
com ela.
Para isso, primeiro pesquise no cache. A função match()
, disponível na interface caches
de nível superior, pesquisa todas as lojas na sua origem ou em um único objeto de cache aberto.
A função match()
recebe uma solicitação HTTP ou um URL como argumento e retorna uma promessa que é resolvida com a resposta associada à chave correspondente.
// Global search on all caches in the current origin
caches.match(urlOrRequest).then(response => {
console.log(response ? response : "It's not in the cache");
});
// Cache-specific search
caches.open("pwa-assets").then(cache => {
cache.match(urlOrRequest).then(response => {
console.log(response ? response : "It's not in the cache");
});
});
Estratégias de armazenamento em cache
Servir arquivos apenas do cache do navegador não se encaixa em todos os casos de uso. Por exemplo, o usuário ou o navegador podem remover o cache. Por isso, você precisa definir suas próprias estratégias para entregar recursos ao seu PWA.
Você não precisa usar apenas uma estratégia de cache. É possível definir diferentes para padrões de URL diferentes. Por exemplo, você pode ter uma estratégia para os recursos mínimos da interface, outra para chamadas de API e uma terceira para URLs de imagem e dados.
Para fazer isso, leia event.request.url
em ServiceWorkerGlobalScope.onfetch
e analise usando expressões regulares ou um padrão de URL. No momento da redação deste artigo, o padrão de URL não é compatível com todas as plataformas.
As estratégias mais comuns são:
- Cache primeiro
- Primeiro, procura uma resposta em cache e volta para a rede se não encontrar uma.
- Priorizar rede
- Primeiro, solicita uma resposta da rede e, se nenhuma for retornada, verifica se há uma resposta no cache.
- Desatualizado ao revalidar
- Serve uma resposta do cache, enquanto em segundo plano solicita a versão mais recente e a salva no cache para a próxima vez que o recurso for solicitado.
- Somente rede
- Sempre responde com uma resposta da rede ou gera um erro. O cache nunca é consultado.
- Somente cache
- Sempre responde com uma resposta do cache ou gera um erro. A rede nunca será consultada. Os recursos que serão veiculados usando essa estratégia precisam ser adicionados ao cache antes de serem solicitados.
Cache primeiro
Usando essa estratégia, o service worker procura a solicitação correspondente no cache e retorna a resposta correspondente se ela estiver armazenada em cache. Caso contrário, ele recupera a resposta da rede (opcionalmente, atualizando o cache para chamadas futuras). Se não houver uma resposta de cache nem uma resposta de rede, a solicitação vai gerar um erro. Como veicular recursos sem acessar a rede tende a ser mais rápido, essa estratégia prioriza a performance em vez da atualização.
self.addEventListener("fetch", event => {
event.respondWith(
caches.match(event.request)
.then(cachedResponse => {
// It can update the cache to serve updated content on the next request
return cachedResponse || fetch(event.request);
}
)
)
});
Prioridade para a rede
Essa estratégia é o espelho da estratégia "Cache First". Ela verifica se a solicitação pode ser atendida pela rede e, se não for possível, tenta recuperá-la do cache. Como "cache first". Se não houver uma resposta de rede nem uma resposta de cache, a solicitação vai gerar um erro. Receber a resposta da rede geralmente é mais lento do que recebê-la do cache. Essa estratégia prioriza o conteúdo atualizado em vez do desempenho.
self.addEventListener("fetch", event => {
event.respondWith(
fetch(event.request)
.catch(error => {
return caches.match(event.request) ;
})
);
});
Desatualizado ao revalidar
A estratégia "obsoleto enquanto revalida" retorna uma resposta em cache imediatamente e verifica a rede para uma atualização, substituindo a resposta em cache se uma for encontrada. Essa estratégia sempre faz uma solicitação de rede, porque, mesmo que um recurso em cache seja encontrado, ela tentará atualizar o que estava no cache com o que foi recebido da rede, para usar a versão atualizada na próxima solicitação. Portanto, essa estratégia oferece uma maneira de aproveitar o serviço rápido da estratégia de cache primeiro e atualizar o cache em segundo plano.
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(cachedResponse => {
const networkFetch = fetch(event.request).then(response => {
// update the cache with a clone of the network response
const responseClone = response.clone()
caches.open(url.searchParams.get('name')).then(cache => {
cache.put(event.request, responseClone)
})
return response
}).catch(function (reason) {
console.error('ServiceWorker fetch failed: ', reason)
})
// prioritize cached response over network
return cachedResponse || networkFetch
}
)
)
})
Somente rede
A estratégia somente de rede é semelhante ao comportamento dos navegadores sem um service worker ou a API Cache Storage. As solicitações só vão retornar um recurso se ele puder ser buscado na rede. Isso geralmente é útil para recursos como solicitações de API somente on-line.
Somente cache
A estratégia de cache somente garante que as solicitações nunca vão para a rede. Todas as solicitações recebidas são respondidas com um item de cache pré-preenchido. O código a seguir usa o manipulador de eventos fetch
com o método match
do armazenamento em cache para responder apenas ao cache:
self.addEventListener("fetch", event => {
event.respondWith(caches.match(event.request));
});
Estratégias personalizadas
Embora as estratégias de cache acima sejam comuns, você é responsável pelo service worker e pela forma como as solicitações são processadas. Se nenhuma delas atender às suas necessidades, crie a sua.
Por exemplo, é possível usar uma estratégia de rede primeiro com um tempo limite para priorizar conteúdo atualizado, mas apenas se a resposta aparecer dentro de um limite definido por você. Também é possível mesclar uma resposta em cache com uma resposta de rede e criar uma resposta complexa do service worker.
Atualização de recursos
Manter os recursos armazenados em cache do seu PWA atualizados pode ser um desafio. Embora a estratégia de atualização em segundo plano seja uma maneira de fazer isso, ela não é a única. No capítulo de atualização, você vai aprender diferentes técnicas para manter o conteúdo e os recursos do app atualizados.