Um aspecto importante dos Progressive Web Apps é que eles são confiáveis. Eles podem carregar ativos rapidamente, mantendo os usuários engajados e oferecendo feedback imediatamente, mesmo em condições ruins de rede. 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 para solicitações da mesma origem e entre origens. Além de solicitações de navegação e recursos, a busca de um service worker instalado permite que visitas à página após o primeiro carregamento de um site sejam renderizadas sem chamadas de rede.
O gerenciador 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.
Seu service worker pode encaminhar uma solicitação para a rede, responder com uma resposta previamente armazenada em cache 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 seu service worker, há duas coisas que você pode fazer: você pode ignorar, o que permite que ela vá para a rede, ou você pode responder a ela. Responder a solicitações no service worker é uma maneira de 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
da seguinte forma:
// fetch event handler in your service worker file
self.addEventListener("fetch", event => {
const response = .... // a response or a Promise of response
event.respondWith(response);
});
Chame respondWith()
de forma síncrona e retorne um objeto Response. Mas não é possível chamar respondWith()
depois que o manipulador de eventos de busca é concluído, como em uma chamada assíncrona. Se for necessário aguardar a resposta completa, é possível transmitir uma promessa para respondWith()
que seja resolvida com uma resposta.
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 fossem de um servidor da Web.
Para gerar uma resposta, crie um novo 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)
Como responder a partir do cache
Agora que você sabe como exibir respostas HTTP de um service worker, é hora de usar a interface de armazenamento em cache para armazenar recursos no dispositivo.
Você pode usar a API de armazenamento em cache para verificar se a solicitação recebida do PWA está disponível no cache e, se estiver, responder a respondWith()
com ela.
Para fazer isso, você precisa primeiro pesquisar no cache. A função match()
, disponível na interface de nível superior caches
, 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
A disponibilização de arquivos apenas do cache do navegador não é adequada para todos os casos de uso. Por exemplo, o usuário ou o navegador pode remover o cache. É por isso que você deve definir suas próprias estratégias de envio de recursos para seu PWA.
Você não está restrito a uma estratégia de armazenamento em cache. É possível definir intervalos diferentes para padrões de URL distintos. Por exemplo, você pode ter uma estratégia para os recursos mínimos de 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. Até o momento, o padrão de URL não é compatível com todas as plataformas.
As estratégias mais comuns são:
- Armazenar em cache primeiro
- Procura primeiro uma resposta em cache e volta para a rede se não for encontrada.
- Priorização da rede
- Solicita uma resposta da rede primeiro e, se nenhuma for retornada, verifica a resposta no cache.
- Desatualizado ao revalidar
- Fornece uma resposta do cache, solicita a versão mais recente em segundo plano 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 erros. O cache nunca é consultado.
- Somente cache
- Sempre responde com uma resposta do cache ou erros. A rede nunca será consultada. Os recursos que serão veiculados com essa estratégia precisam ser adicionados ao cache antes de serem solicitados.
Cache primeiro
Com essa estratégia, o service worker procura a solicitação correspondente no cache e retorna a Resposta correspondente se ela estiver armazenada. 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 de rede, a solicitação apresentará um erro. Como a veiculação de recursos sem acessar a rede tende a ser mais rápida, 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 da rede
Essa estratégia é o espelho da estratégia de cache priorizando. Ela verifica se a solicitação pode ser atendida pela rede e, se não pode, tenta recuperá-la do cache. Marque o cache primeiro. Se não houver uma resposta de rede nem de cache, a solicitação apresentará um erro. Receber a resposta da rede geralmente é mais lenta do que a 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 descontinuada ao revalidar retorna imediatamente uma resposta armazenada em cache e, em seguida, verifica se há uma atualização na rede, substituindo a resposta armazenada em cache, se ela for encontrada. Essa estratégia sempre faz uma solicitação de rede, porque mesmo se um recurso armazenado em cache for encontrado, ele vai 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 se beneficiar da veiculação rápida da estratégia que prioriza o cache 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 rede" é semelhante a como os navegadores se comportam 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 somente em cache garante que as solicitações nunca cheguem à rede. Todas as solicitações recebidas serã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 acima sejam comuns, você é responsável pelo service worker e como as solicitações são processadas. Se nenhuma dessas opções funcionar para suas necessidades, crie a sua.
É possível, por exemplo, usar uma estratégia de priorização da rede com tempo limite para priorizar o conteúdo atualizado, mas somente se a resposta aparecer dentro de um limite definido por você. Também é possível mesclar uma resposta armazenada em cache com uma resposta de rede e criar uma resposta complexa a partir do service worker.
Atualizando recursos
Manter os recursos em cache do PWA atualizados pode ser um desafio. Embora a estratégia desatualizada enquanto revalide seja uma maneira de fazer isso, não é a única. No capítulo "Atualizar", você vai aprender diferentes técnicas para manter o conteúdo e os recursos do app atualizados.