Presentamos la recuperación en segundo plano

Jake Archibald
Jake Archibald

En 2015, presentamos la sincronización en segundo plano, que permite que el service worker aplace el trabajo hasta que el usuario tenga conectividad. Esto significa que el usuario podría escribir un mensaje, presionar Enviar y salir del sitio con la certeza de que el mensaje se enviará ahora o cuando tengan conectividad.

Es una función útil, pero requiere que el service worker esté activo mientras se realiza la recuperación. Eso no es un problema para las porciones breves de trabajo, como enviar un mensaje, pero si la tarea tarda demasiado, el navegador cerrará el service worker; de lo contrario, pone en riesgo la privacidad y la batería del usuario.

¿Qué sucede si necesitas descargar algo que podría llevar mucho tiempo, como una película, podcasts o niveles de un juego? Para eso, se usa la recuperación en segundo plano.

La recuperación en segundo plano está disponible de forma predeterminada a partir de Chrome 74.

A continuación, se incluye una demostración rápida de dos minutos que muestra el estado tradicional de las cosas en comparación con el uso de la recuperación en segundo plano:

Prueba la demostración por tu cuenta y explora el código.

Cómo funciona

Una recuperación en segundo plano funciona de la siguiente manera:

  1. Le indicas al navegador que realice un grupo de recuperaciones en segundo plano.
  2. El navegador recupera esos datos y muestra el progreso al usuario.
  3. Cuando la recuperación se completa o falla, el navegador abre el service worker y activa un evento para indicarte lo que sucedió. Aquí es donde decides qué hacer con las respuestas, si corresponde.

Si el usuario cierra las páginas de tu sitio después del paso 1, no hay problema, la descarga continuará. Debido a que la recuperación es muy visible y se puede anular fácilmente, no existe el problema de privacidad de una tarea de sincronización demasiado larga en segundo plano. Debido a que el service worker no se ejecuta constantemente, no existe la preocupación de que pueda abusar del sistema, por ejemplo, mediante la minería de criptomonedas en segundo plano.

En algunas plataformas (como Android), es posible que el navegador se cierre después del paso 1, ya que puede transferir la recuperación al sistema operativo.

Si el usuario inicia la descarga mientras está sin conexión o se queda sin conexión durante la descarga, la recuperación en segundo plano se pausará y reanudará más tarde.

La API

Detección de atributos

Al igual que con cualquier función nueva, te recomendamos que detectes si el navegador la admite. Para la recuperación en segundo plano, es tan simple como se muestra a continuación:

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

Inicia una recuperación en segundo plano

La API principal bloquea el registro de un service worker, así que primero asegúrate de haber registrado un service worker. Luego:

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 toma tres argumentos:

Parámetros
id string
identifica de forma única esta recuperación en segundo plano.

Se rechazará backgroundFetch.fetch si el ID coincide con una recuperación en segundo plano existente.

requests Array<Request|string>
Elementos que se deben recuperar Las strings se tratarán como URLs y se convertirán en Request a través de new Request(theString).

Puedes recuperar elementos de otros orígenes, siempre y cuando los recursos lo permitan mediante CORS.

Nota: Por el momento, Chrome no admite solicitudes que requieran una comprobación previa de CORS.

options Es un objeto que puede incluir lo siguiente:
options.title string
Es un título que se mostrará en el navegador junto con el progreso.
options.icons Array<IconDefinition>
Arreglo de objetos con `src`, `size` y `type`.
options.downloadTotal number
El tamaño total de los cuerpos de la respuesta (después de que se descomprimen con gzip).

Aunque es opcional, te recomendamos que lo proporciones. Se usa para indicarle al usuario el tamaño de la descarga y brindarle información sobre el progreso. Si no proporcionas esta información, el navegador le indicará al usuario que el tamaño es desconocido y, como resultado, es más probable que este anule la descarga.

Si las descargas de recuperación en segundo plano superan la cantidad indicada aquí, se anulará. No hay problema si la descarga es inferior a la downloadTotal, por lo que, si no sabes con certeza cuál será el total de la descarga, te recomendamos ser cauteloso.

backgroundFetch.fetch muestra una promesa que se resuelve con una BackgroundFetchRegistration. Cubriré los detalles más adelante. La promesa se rechaza si el usuario inhabilitó las descargas o si uno de los parámetros proporcionados no es válido.

Proporcionar muchas solicitudes para una sola recuperación en segundo plano te permite combinar elementos que, desde la lógica, son una sola cosa para el usuario. Por ejemplo, una película se puede dividir en miles de recursos (habitual con MPEG-DASH) y puede incluir recursos adicionales, como imágenes. Un nivel de un juego puede distribuirse a través de muchos recursos de JavaScript, imagen y audio. Sin embargo, para el usuario, es solo "la película" o "el nivel".

Cómo obtener una recuperación en segundo plano existente

Puedes obtener una recuperación en segundo plano existente como la siguiente:

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

Pasar el id de la recuperación en segundo plano que desees get muestra undefined si no hay una recuperación en segundo plano activa con ese ID.

Una recuperación en segundo plano se considera "activa" desde el momento en que se registra, hasta que tiene éxito, falla o se anula.

Puedes obtener una lista de todas las recuperaciones en segundo plano activas con getIds:

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

Registros de recuperación en segundo plano

Un BackgroundFetchRegistration (bgFetch en los ejemplos anteriores) tiene lo siguiente:

Propiedades
id string
El ID de la recuperación en segundo plano.
uploadTotal number
La cantidad de bytes que se enviarán al servidor.
uploaded number
La cantidad de bytes enviados correctamente.
downloadTotal number
Es el valor proporcionado cuando se registró la recuperación en segundo plano, o cero.
downloaded number
La cantidad de bytes recibidos correctamente.

Este valor puede disminuir. Por ejemplo, si se interrumpe la conexión y no se puede reanudar la descarga, el navegador reiniciará la recuperación de ese recurso desde cero.

result

Uno de los siguientes:

  • "": La recuperación en segundo plano está activa, por lo que aún no hay resultados.
  • "success": Se realizó correctamente la recuperación en segundo plano.
  • "failure": Falló la recuperación en segundo plano. Este valor solo aparece cuando falla por completo la recuperación en segundo plano, ya que el navegador no puede reintentar o reanudar.
failureReason

Uno de los siguientes:

  • "": La recuperación en segundo plano no falló.
  • "aborted": El usuario anuló la recuperación en segundo plano, o se llamó a abort().
  • "bad-status": Una de las respuestas tenía un estado incorrecto, p.ej., 404.
  • "fetch-error": Una de las recuperaciones falló por otro motivo, p.ej., CORS, MIX, una respuesta parcial no válida o una falla general de la red debido a una recuperación que no se puede reintentar.
  • "quota-exceeded": Se alcanzó la cuota de almacenamiento durante la recuperación en segundo plano.
  • "download-total-exceeded": Se superó el `downloadTotal` proporcionado.
recordsAvailable boolean
¿Se puede acceder a las solicitudes o respuestas subyacentes?

Una vez que esto se establezca como falso, no se podrá usar match ni matchAll.

Métodos
abort() Muestra Promise<boolean>
Anula la recuperación en segundo plano.

La promesa que se muestra se resuelve con true si la recuperación se anuló correctamente.

matchAll(request, opts) Muestra Promise<Array<BackgroundFetchRecord>>
Obtén las solicitudes y respuestas.

Aquí los argumentos son los mismos que la API de caché. Si llamas sin argumentos, se muestra una promesa para todos los registros.

Consulte la siguiente información para obtener más detalles.

match(request, opts) Muestra Promise<BackgroundFetchRecord>
Como se muestra arriba, pero se resuelve con la primera coincidencia.
Eventos
progress Se activa cuando cambia alguno de uploaded, downloaded, result o failureReason.

Seguimiento del progreso

Esto puede hacerse a través del evento progress. Recuerda que downloadTotal es cualquier valor que hayas proporcionado o 0 si no proporcionaste uno.

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}%`);
});

Obtén las solicitudes y las respuestas

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 es un BackgroundFetchRecord y se ve de la siguiente manera:

Propiedades
request Request
La solicitud que se proporcionó.
responseReady Promise<Response>
La respuesta recuperada.

La respuesta está detrás de una promesa porque es posible que aún no se haya recibido. La promesa se rechazará si falla la recuperación.

Eventos de service worker

Eventos
backgroundfetchsuccess Se recuperó todo correctamente.
backgroundfetchfailure Se produjo un error en una o más de las recuperaciones.
backgroundfetchabort No se pudieron realizar una o más recuperaciones.

Esto solo es muy útil si quieres realizar una limpieza de datos relacionados.

backgroundfetchclick El usuario hizo clic en la IU de progreso de la descarga.

Los objetos de evento tienen lo siguiente:

Propiedades
registration BackgroundFetchRegistration
Métodos
updateUI({ title, icons }) Te permite cambiar el título o los íconos que configuraste inicialmente. Esto es opcional, pero te permite proporcionar más contexto si es necesario. Solo puedes hacerlo *una vez* durante los eventos backgroundfetchsuccess y backgroundfetchfailure.

Reacción ante el éxito o el fracaso

Ya vimos el evento progress, pero solo es útil cuando el usuario tiene una página abierta en tu sitio. El principal beneficio de la recuperación en segundo plano es que las cosas continúan funcionando después de que el usuario abandona la página o incluso cierra el navegador.

Si la recuperación en segundo plano se completa de forma correcta, el service worker recibirá el evento backgroundfetchsuccess y event.registration será el registro de la recuperación en segundo plano.

Después de este evento, ya no se puede acceder a las solicitudes ni respuestas recuperadas, por lo que si deseas conservarlas, muévelas a un lugar como la API de caché.

Al igual que con la mayoría de los eventos de service worker, usa event.waitUntil para que este sepa cuándo se completó el evento.

Por ejemplo, en tu 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!' });
  }());
});

Es posible que la falla se haya reducido a un solo error 404, que puede no haber sido importante para ti, por lo que quizás valga la pena copiar algunas respuestas en una caché, como se indicó anteriormente.

Reaccionar al clic

Se puede hacer clic en la IU que muestra el progreso y el resultado de la descarga. El evento backgroundfetchclick en el service worker te permite reaccionar a esto. Como se muestra más arriba, event.registration será el registro de recuperación en segundo plano.

Lo común para este evento es abrir una ventana:

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

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

Recursos adicionales

Corrección: En una versión anterior de este artículo, se hacía referencia de manera incorrecta a la recuperación en segundo plano como un “estándar web”. Por el momento, la API no se encuentra en el segmento estándar. Puedes encontrar la especificación en WICG como un informe de grupo de comunidad en borrador.