Qualquer pessoa que tenha usado service workers
pode dizer que eles são assíncronos. Eles dependem exclusivamente
de interfaces baseadas em eventos, como
FetchEvent
,
e usam promessas para
sinalizar quando as operações assíncronas são concluídas.
A assincronia é igualmente importante, embora menos visível para o desenvolvedor, quando se trata de respostas fornecidas pelo manipulador de eventos de busca de um worker de serviço. As respostas de streaming são o padrão ouro aqui: elas permitem que a página que fez a solicitação original comece a trabalhar com a resposta assim que o primeiro bloco de dados estiver disponível e, possivelmente, use analisadores otimizados para streaming para mostrar o conteúdo de forma progressiva.
Ao escrever seu próprio manipulador de eventos fetch
, é comum transmitir ao método
respondWith()
um Response
(ou uma promessa de Response
) que você recebe por
fetch()
ou caches.match()
,
e pronto. A boa notícia é que as Response
s criadas por esses dois
métodos já podem ser transmitidas por streaming. A má notícia é que as Response
s
construídas" manualmente
não podem ser transmitidas, pelo menos até agora. É aí que a
API Streams entra em cena.
Fluxos?
Um stream é uma origem de dados que pode ser
criada e manipulada de forma incremental,
e fornece uma interface para ler ou gravar blocos de dados assíncronos,
dos quais apenas um subconjunto pode estar disponível na memória a qualquer momento. Por enquanto,
nosso interesse está em ReadableStream
s,
que podem ser usados para construir um objeto
Response
que é transmitido para fetchEvent.respondWith()
:
self.addEventListener('fetch', event => {
var stream = new ReadableStream({
start(controller) {
if (/* there's more data */) {
controller.enqueue(/* your data here */);
} else {
controller.close();
}
});
});
var response = new Response(stream, {
headers: {'content-type': /* your content-type here */}
});
event.respondWith(response);
});
A página que acionou o evento fetch
vai receber uma resposta de streaming
assim que event.respondWith()
for chamado e vai continuar lendo
esse stream enquanto o worker de serviço continuar enqueue()
ndo outros
dados. A resposta que flui do service worker para a página é realmente
assíncrona, e temos controle total sobre o preenchimento do stream.
Usos reais
Você provavelmente notou que o exemplo anterior tinha alguns comentários de marcador de posição
/* your data here */
e não tinha muitos detalhes de implementação.
Então, como seria um exemplo real?
Jake Archibald (não é surpresa!) tem um
ótimo exemplo
de como usar streams para unir uma resposta HTML de vários snippets HTML
em cache, junto com dados "ao vivo" transmitidos por fetch()
. Neste caso, o conteúdo
do blog dele.
A vantagem de usar uma resposta de streaming, como Jake explica, é que o navegador pode analisar e renderizar o HTML conforme ele é transmitido, incluindo o bit inicial que é carregado rapidamente do cache, sem ter que esperar que todo o conteúdo do blog seja buscado. Isso aproveita ao máximo os recursos de renderização progressiva de HTML do navegador. Outros recursos que também podem ser renderizados progressivamente, como alguns formatos de imagem e vídeo, também podem se beneficiar dessa abordagem.
Fluxos? Ou shells de apps?
As práticas recomendadas atuais sobre o uso de service workers para impulsionar seus apps da Web se concentram em um modelo de shell do app + conteúdo dinâmico. Essa abordagem depende do armazenamento em cache agressivo do "shell" do seu aplicativo da Web, ou seja, o HTML, JavaScript e CSS mínimos necessários para mostrar a estrutura e o layout, e, em seguida, carregar o conteúdo dinâmico necessário para cada página específica por meio de uma solicitação do lado do cliente.
As transmissões oferecem uma alternativa ao modelo de shell do app, em que há uma resposta HTML mais completa transmitida para o navegador quando um usuário navega até uma nova página. A resposta transmitida pode usar recursos armazenados em cache, para que possa ainda fornecer o bloco inicial de HTML rapidamente, mesmo off-line. No entanto, ela acaba parecendo mais com corpos de resposta renderizados pelo servidor. Por exemplo, se o app da Web for executado por um sistema de gerenciamento de conteúdo que renderiza HTML no servidor unindo modelos parciais, esse modelo se traduz diretamente no uso de respostas de streaming, com a lógica de modelagem replicada no worker de serviço em vez do servidor. Como o vídeo abaixo demonstra, para esse caso de uso, a vantagem de velocidade que as respostas transmitidas oferecem pode ser impressionante:
Uma vantagem importante do streaming de toda a resposta HTML, explicando por que ela é a alternativa mais rápida no vídeo, é que o HTML renderizado durante a solicitação de navegação inicial pode aproveitar ao máximo o analisador de HTML de streaming do navegador. Os blocos de HTML inseridos em um documento depois que a página é carregada (como é comum no modelo de shell do app) não podem aproveitar essa otimização.
Portanto, se você estiver nos estágios de planejamento da implementação do service worker, qual modelo adotar: respostas transmitidas em streaming renderizadas progressivamente ou um shell leve associado a uma solicitação do lado do cliente para conteúdo dinâmico? A resposta é, não surpreendentemente, que depende: se você tem uma implementação existente que depende de um CMS e modelos parciais (vantagem: stream); se você espera payloads HTML únicos e grandes que se beneficiariam da renderização progressiva (vantagem: stream); se o melhor modelo para seu app da Web é um aplicativo de página única (vantagem: App Shell); e se você precisa de um modelo que seja compatível com várias versões estáveis de vários navegadores (vantagem: App Shell).
Ainda estamos no início das respostas de streaming com base em worker de serviço e esperamos que os diferentes modelos amadureçam e que mais ferramentas sejam desenvolvidas para automatizar casos de uso comuns.
Informações detalhadas sobre transmissões
Se você estiver criando seus próprios fluxos legíveis, chamar
controller.enqueue()
indiscriminadamente pode não ser suficiente ou
eficiente. Jake explica em detalhes
como os métodos start()
, pull()
e cancel()
podem ser usados em conjunto
para criar um fluxo de dados personalizado para seu caso de uso.
Para quem quer ainda mais detalhes, a especificação de streams tem tudo o que você precisa.
Compatibilidade
Foi adicionado suporte para a construção de um objeto Response
dentro de um service worker usando um
ReadableStream
como fonte no
Chrome 52.
A implementação do service worker do Firefox ainda não oferece suporte a respostas com
ReadableStream
s, mas há um
bug de rastreamento relevante para o suporte à API
Streams que você pode seguir.
O progresso do suporte à API Streams sem prefixo no Edge, além do suporte ao service worker, pode ser acompanhado na página de status da plataforma da Microsoft.