Um dos principais aspectos dos Progressive Web Apps é que eles são confiáveis. 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 worker de serviço.
O evento de busca
O evento fetch
permite interceptar todas as solicitações de rede feitas pelo PWA no escopo do worker de serviço, para solicitações de mesma origem e de origem cruzada. Além das solicitações de navegação e de recursos, a busca em 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 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.
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 seu worker de serviço, você pode fazer duas coisas: ignorá-la, o que permite que ela vá para a rede, ou responder a ela. A resposta às 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
, como este:
// 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 for concluído, como em uma chamada assíncrona. Se você precisar esperar pela 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. 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 novo objeto Response
e defina o corpo e as 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)
Respostas do cache
Agora que você sabe como veicular respostas HTTP de um service worker, é hora de usar a interface de armazenamento de cache para armazenar recursos no dispositivo.
É possível usar a API de armazenamento em cache para verificar se a solicitação recebida do PWA está disponível no cache. Se estiver, responda a respondWith()
com ela.
Para isso, primeiro você precisa pesquisar 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
O envio de arquivos apenas do cache do navegador não é adequado para todos os casos de uso. Por exemplo, o usuário ou o navegador pode remover o cache. Por isso, você precisa definir suas próprias estratégias para fornecer recursos para a PWA.
Você não fica restrito a uma estratégia de armazenamento em cache. Você pode definir padrões diferentes para padrões de URL diferentes. Por exemplo, você pode ter uma estratégia para os recursos mínimos da interface, outra para as chamadas de API e uma terceira para URLs de imagens e dados.
Para fazer isso, leia event.request.url
em ServiceWorkerGlobalScope.onfetch
e analise-o usando expressões regulares ou um padrão de URL. No momento, o Padrão de URL não é compatível com todas as plataformas.
As estratégias mais comuns são:
- Cache First
- Procura primeiro uma resposta armazenada em cache e volta para a rede se nenhuma for encontrada.
- Rede em primeiro lugar
- Solicita uma resposta da rede primeiro e, se nenhuma for retornada, verifica a resposta no cache.
- Desatualizado durante a revalidação
- Exibe 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 erros. 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 (se preferir, atualizando o cache para chamadas futuras). Se não houver uma resposta de cache nem de rede, a solicitação vai gerar 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 atualidade.
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);
}
)
)
});
Rede em primeiro lugar
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 o cache primeiro. Se não houver uma resposta de rede nem de cache, a solicitação vai gerar um erro. A resposta da rede geralmente é mais lenta do que a do cache. Essa estratégia prioriza o conteúdo atualizado em vez da performance.
self.addEventListener("fetch", event => {
event.respondWith(
fetch(event.request)
.catch(error => {
return caches.match(event.request) ;
})
);
});
Desatualizado durante a revalidação
A estratégia de revalidação de dados desaturos retorna uma resposta em cache imediatamente e, em seguida, verifica a rede em busca de uma atualização, substituindo a resposta em cache, se encontrada. Essa estratégia sempre faz uma solicitação de rede, porque, mesmo que um recurso em cache seja 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. Essa estratégia oferece uma maneira de você aproveitar a exibição rápida 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 de rede exclusiva é 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 da rede. Isso geralmente é útil para recursos como solicitações de API somente on-line.
Somente cache
A estratégia de uso exclusivo do cache garante que as solicitações nunca vão para a rede. Todas as solicitações recebidas são respondidas com um item de cache preenchido. O código abaixo usa o manipulador de eventos fetch
com o método match
do armazenamento de cache para responder apenas ao cache:
self.addEventListener("fetch", event => {
event.respondWith(caches.match(event.request));
});
Estratégias personalizadas
Embora as estratégias de armazenamento em cache acima sejam comuns, você é responsável pelo service worker e pela forma como as solicitações são tratadas. Se nenhuma delas atender às suas necessidades, crie a sua.
Por exemplo, é possível usar uma estratégia de rede em primeiro lugar com um tempo limite para priorizar o conteúdo atualizado, mas somente se a resposta aparecer dentro de um limite definido. Também é possível mesclar uma resposta em cache com uma resposta de rede e criar uma resposta complexa do service worker.
Como atualizar recursos
Manter os recursos em cache da sua PWA atualizados pode ser um desafio. Embora a estratégia de revalidação de dados desaturados seja uma maneira de fazer isso, 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.