Apresentamos a Busca em segundo plano

Jake Archibald
Jake Archibald

Em 2015, lançamos a sincronização em segundo plano, que permite que o service worker adie o trabalho até que o usuário tenha conectividade. Isso significa que o usuário pode digitar mensagem, clicar em "Enviar" e sair do site sabendo que a mensagem será enviada agora ou quando tenham conectividade.

É um recurso útil, mas exige que o service worker esteja ativo pela duração do buscar. Isso não é um problema para pequenas partes de trabalho, como enviar uma mensagem, mas se a tarefa leva muito tempo o navegador eliminará o service worker, caso contrário é um risco para a privacidade do usuário e bateria.

E se você precisar fazer o download de algo que pode demorar muito, como um filme, podcasts ou fases de um jogo. É para isso que serve a Busca em segundo plano.

A busca em segundo plano está disponível por padrão desde o Chrome 74.

Aqui está uma demonstração rápida de dois minutos que mostra o estado tradicional das coisas em comparação com o uso da Busca em segundo plano:

Experimente a demonstração e procure o código.

Como funciona

Uma busca em segundo plano funciona assim:

  1. Você informa ao navegador para executar um grupo de buscas em segundo plano.
  2. O navegador busca esses itens, mostrando o progresso ao usuário.
  3. Após a conclusão ou falha da busca, o navegador abre seu service worker e aciona um evento para contar o que aconteceu. É aqui que você decide o que fazer com as respostas, se houver.

Se o usuário fechar as páginas do site após a etapa 1, tudo bem, o download continuará. Devido ao a busca for altamente visível e facilmente cancelada, não há a preocupação de privacidade de sincronização em segundo plano. Como o service worker não está em execução constantemente, não há o problema que pode abusar do sistema, por exemplo, minerar bitcoin em segundo plano.

Em algumas plataformas (como Android), é possível que o navegador feche após a etapa 1, pois o navegador pode transferir a busca ao sistema operacional.

Se o usuário iniciar o download enquanto estiver off-line, ou ficar off-line durante o download, o plano de fundo busca será pausada e retomada mais tarde.

A API

Detecção de recursos

Como acontece com qualquer novo recurso, o objetivo é detectar se o navegador é compatível. Para a Busca em segundo plano, tão simples quanto:

if ('BackgroundFetchManager' in self) {
  // This browser supports Background Fetch!
}

Como iniciar uma busca em segundo plano

A API principal trava o registro de um service worker. Portanto, não deixe de registrar um service worker primeiro. Em seguida:

navigator.serviceWorker.ready.then(async (swReg) => {
  const bgFetch = await swReg.backgroundFetch.fetch('my-fetch', ['/ep-5.mp3', 'ep-5-artwork.jpg'], {
    title: 'Episode 5: Interesting things.',
    icons: [{
      sizes: '300x300',
      src: '/ep-5-icon.png',
      type: 'image/png',
    }],
    downloadTotal: 60 * 1024 * 1024,
  });
});

backgroundFetch.fetch usa três argumentos:

Parâmetros
id string
identifica de forma exclusiva essa busca em segundo plano.

O backgroundFetch.fetch será rejeitado se o ID corresponder a um plano de fundo existente buscar.

requests Array<Request|string>
Itens a serem buscados. As strings serão tratadas como URLs e transformadas em Request por new Request(theString).

É possível buscar itens de outras origens, desde que os recursos permitam CORS:

Observação: no momento, o Chrome não é compatível com solicitações exigem uma simulação do CORS.

options Um objeto que pode incluir o seguinte:
options.title string
Um título para o navegador exibir ao longo do progresso.
options.icons Array<IconDefinition>
Uma matriz de objetos com "src", "size" e "type".
options.downloadTotal number
O tamanho total dos corpos das respostas (após serem descompactadas com gzip).

Embora isso seja opcional, recomendamos que você o forneça. É usada para dizer ao usuário o tamanho do download e fornecer informações sobre o andamento. Se você não fornecer isso, o navegador dirá ao usuário que o tamanho é desconhecido e, como resultado, o usuário pode ser mais provavelmente abortará o download.

Se os downloads da busca em segundo plano excederem o número indicado aqui, eles serão cancelados. Está tudo bem se o download for menor que downloadTotal. Portanto, se você não definir o total de downloads, é melhor pecar por cautela.

backgroundFetch.fetch retorna uma promessa que é resolvida com uma BackgroundFetchRegistration. Vou abordar os detalhes disso mais tarde. A promessa será rejeitada se o usuário tiver desativado os downloads ou dos parâmetros fornecidos é inválido.

Fornecer muitas solicitações para uma única busca em segundo plano permite combinar coisas que são logicamente uma algo único para o usuário. Por exemplo, um filme pode ser dividido em milhares de recursos (comum MPEG-DASH), e vêm com recursos adicionais, como imagens. Um nível de um jogo pode ser distribuído por vários JavaScript, imagem e recursos de áudio. Mas, para o usuário, é apenas "o filme" ou "o nível".

Como receber uma busca em segundo plano existente

É possível receber uma busca em segundo plano já existente como esta:

navigator.serviceWorker.ready.then(async (swReg) => {
  const bgFetch = await swReg.backgroundFetch.get('my-fetch');
});

... passando o id da busca em segundo plano que você quer. get retorna undefined se não houver busca ativa em segundo plano com esse ID.

Uma busca em segundo plano é considerada "ativa" a partir do momento do registro até que dê certo, falhar ou for abortada.

Confira uma lista de todas as buscas em segundo plano ativas usando getIds:

navigator.serviceWorker.ready.then(async (swReg) => {
  const ids = await swReg.backgroundFetch.getIds();
});

Registros de busca em segundo plano

Uma BackgroundFetchRegistration (bgFetch nos exemplos acima) tem o seguinte:

Propriedades
id string
O ID da busca em segundo plano.
uploadTotal number
O número de bytes a serem enviados ao servidor.
uploaded number
O número de bytes enviados.
downloadTotal number
O valor fornecido quando a busca em segundo plano foi registrada ou zero.
downloaded number
O número de bytes recebidos.

Esse valor pode diminuir. Por exemplo, se a conexão cair e o download não puder ser retomado e nesse caso o navegador reinicia a busca desse recurso do zero.

result

Opções:

  • "": a busca em segundo plano está ativa, então ainda não há resultado.
  • "success": a busca em segundo plano foi bem-sucedida.
  • "failure": falha na busca em segundo plano. Esse valor só aparece quando a busca em segundo plano falhará totalmente, pois o navegador não pode tentar novamente/retomar.
failureReason

Opções:

  • "": a busca em segundo plano não falhou.
  • "aborted": a busca em segundo plano foi cancelada pelo usuário. abort() foi chamado.
  • "bad-status": uma das respostas apresentou um status ruim, por exemplo, 404.
  • "fetch-error": uma das buscas falhou por algum outro motivo, por exemplo, CORS, MIX, uma resposta parcial inválida ou uma falha geral de rede para uma busca que e não pode ser repetida.
  • "quota-exceeded": a cota de armazenamento foi atingida em segundo plano buscar.
  • "download-total-exceeded": o "downloadTotal" fornecido era excedido.
recordsAvailable boolean
É possível acessar as solicitações/respostas subjacentes?

Se for falso, match e matchAll não poderão ser usados.

Métodos
abort() Retorna Promise<boolean>
Cancela a busca em segundo plano.

A promessa retornada é resolvida com "true" se a busca for cancelada.

matchAll(request, opts) Retorna Promise<Array<BackgroundFetchRecord>>
Receber as solicitações e respostas.

Os argumentos aqui são os mesmos que o cache API. Chamar sem argumentos retorna uma promessa para todos os registros.

Veja mais detalhes abaixo.

match(request, opts) Retorna Promise<BackgroundFetchRecord>
Como acima, mas resolve com a primeira correspondência.
Eventos
progress Disparado quando uploaded, downloaded, result ou failureReason alteração.

Acompanhamento do progresso

Isso pode ser feito no evento progress. Lembre-se de que downloadTotal é o valor que você fornecido, ou 0 se você não tiver fornecido um valor.

bgFetch.addEventListener('progress', () => {
  // If we didn't provide a total, we can't provide a %.
  if (!bgFetch.downloadTotal) return;

  const percent = Math.round(bgFetch.downloaded / bgFetch.downloadTotal * 100);
  console.log(`Download progress: ${percent}%`);
});

Como receber solicitações e respostas

bgFetch.match('/ep-5.mp3').then(async (record) => {
  if (!record) {
    console.log('No record found');
    return;
  }

  console.log(`Here's the request`, record.request);
  const response = await record.responseReady;
  console.log(`And here's the response`, response);
});

record é um BackgroundFetchRecord e tem esta aparência:

Propriedades
request Request
A solicitação que foi enviada.
responseReady Promise<Response>
A resposta buscada.

A resposta está por trás de uma promessa porque talvez ela ainda não tenha sido recebida. A promessa será rejeitada se a busca falhar.

Eventos de service worker

Eventos
backgroundfetchsuccess A busca foi concluída.
backgroundfetchfailure Falha em uma ou mais buscas.
backgroundfetchabort Falha em uma ou mais buscas.

Isso é realmente útil apenas se você deseja realizar uma limpeza de dados relacionados.

backgroundfetchclick O usuário clicou na interface de progresso do download.

Os objetos de evento têm o seguinte:

Propriedades
registration BackgroundFetchRegistration
Métodos
updateUI({ title, icons }) Permite mudar o título/ícones definidos inicialmente. Isso é opcional, mas permite que você fornecer mais contexto, se necessário. Você só pode fazer isso *uma vez* durante Eventos backgroundfetchsuccess e backgroundfetchfailure.

Reagir ao sucesso/fracasso

Já vimos o evento progress, mas ele só é útil enquanto o usuário está com uma página aberta para seu site. O principal benefício da busca em segundo plano é que as coisas continuam funcionando depois que o usuário sai da ou até mesmo fechar o navegador.

Se a busca em segundo plano for concluída, o service worker receberá a backgroundfetchsuccess e event.registration serão o registro de busca em segundo plano.

Após esse evento, as solicitações e respostas buscadas não estarão mais acessíveis, portanto, se você quiser mova-os para algum lugar como a API de cache.

Como na maioria dos eventos de service worker, use event.waitUntil para que o service worker saiba quando o evento é concluída.

Por exemplo, no seu service worker:

addEventListener('backgroundfetchsuccess', (event) => {
  const bgFetch = event.registration;

  event.waitUntil(async function() {
    // Create/open a cache.
    const cache = await caches.open('downloads');
    // Get all the records.
    const records = await bgFetch.matchAll();
    // Copy each request/response across.
    const promises = records.map(async (record) => {
      const response = await record.responseReady;
      await cache.put(record.request, response);
    });

    // Wait for the copying to complete.
    await Promise.all(promises);

    // Update the progress notification.
    event.updateUI({ title: 'Episode 5 ready to listen!' });
  }());
});

A falha pode ter se reduzido a um único erro 404, que pode não ter sido importante para você, então pode ainda vale a pena copiar algumas respostas em um cache, como mostrado acima.

Reagir ao clique

É possível clicar na interface que exibe o progresso do download e o resultado. O evento backgroundfetchclick em o service worker permite que você reaja a isso. Assim como acima, event.registration será o plano de fundo registro de busca.

Normalmente, esse evento é abrir uma janela:

addEventListener('backgroundfetchclick', (event) => {
  const bgFetch = event.registration;

  if (bgFetch.result === 'success') {
    clients.openWindow('/latest-podcasts');
  } else {
    clients.openWindow('/download-progress');
  }
});

Outros recursos

Correção: uma versão anterior deste artigo fez referência incorreta à Busca em segundo plano de "padrão da Web". No momento, a API não está na faixa padrão. A especificação pode ser encontrada no WICG como um rascunho de relatório do grupo da comunidade.