Presentamos la recuperación en segundo plano

En 2015, presentamos la sincronización en segundo plano, que permite que el trabajador de servicio 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 sabiendo que el mensaje se enviará ahora o cuando tenga conectividad.

Es una función útil, pero requiere que el trabajador de servicio esté activo durante la recuperación. Eso no es un problema para tareas cortas, como enviar un mensaje, pero si la tarea tarda demasiado, el navegador finalizará el trabajador de servicio. De lo contrario, es un riesgo para la privacidad y la batería del usuario.

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

La actualización en segundo plano está disponible de forma predeterminada desde Chrome 74.

Esta es una breve demostración de dos minutos que muestra el estado tradicional en comparación con el uso de la actualización en segundo plano:

Prueba la demostración 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 elementos y muestra el progreso al usuario.
  3. Una vez que se complete o falle la recuperación, el navegador abrirá tu trabajador de servicio y activará un evento para informarte 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 te preocupes, la descarga continuará. Debido a que la recuperación es muy visible y se puede anular con facilidad, no existe la preocupación de privacidad de una tarea de sincronización en segundo plano demasiado larga. Como el trabajador de servicio no se ejecuta constantemente, no hay preocupación de que pueda abusar del sistema, como minar bitcoins en segundo plano.

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

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

La API

Detección de funciones

Al igual que con cualquier función nueva, debes detectar si el navegador la admite. Para la recuperación en segundo plano, es tan simple como lo siguiente:

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

Cómo iniciar una recuperación en segundo plano

La API principal se suspende en un registro de proceso de trabajo de servicio, así que asegúrate de registrar uno primero. 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 actualización en segundo plano.

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

requests Array<Request|string>
Los elementos que se recuperarán. Las cadenas 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 que los recursos lo permitan a través de CORS.

Nota: Actualmente, Chrome no admite solicitudes que requerirían una verificación previa de CORS.

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

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

Si las descargas de recuperación en segundo plano superan el número proporcionado aquí, se anulará. No hay problema si la descarga es más pequeña que el downloadTotal, por lo que, si no estás seguro de cuál será el total de la descarga, es mejor pecar con precaución.

backgroundFetch.fetch muestra una promesa que se resuelve con un BackgroundFetchRegistration. Analizaré 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, lógicamente, son un elemento único para el usuario. Por ejemplo, una película puede dividirse en miles de recursos (algo típico de MPEG-DASH) y venir con recursos adicionales, como imágenes. Un nivel de un juego podría distribuirse en 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 actualización en segundo plano existente de la siguiente manera:

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

…pasando el id de la actualización en segundo plano que deseas. get muestra undefined si no hay ninguna actualización en segundo plano activa con ese ID.

Una actualización en segundo plano se considera "activa" desde el momento en que se registra hasta que se realiza correctamente, falla o se cancela.

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 actualización en segundo plano.
uploadTotal number
Es la cantidad de bytes que se enviarán al servidor.
uploaded number
Es la cantidad de bytes que se enviaron correctamente.
downloadTotal number
El valor proporcionado cuando se registró la actualización en segundo plano o cero.
downloaded number
Es la cantidad de bytes recibidos correctamente.

Es posible que este valor disminuya. Por ejemplo, si se cae la conexión y no se puede reanudar la descarga, en cuyo caso el navegador reinicia la recuperación de ese recurso desde cero.

result

Uno de los siguientes:

  • "": La actualización en segundo plano está activa, por lo que aún no hay resultados.
  • "success": La recuperación en segundo plano se realizó correctamente.
  • "failure": No se pudo realizar la recuperación en segundo plano. Este valor solo aparece cuando la actualización en segundo plano falla por completo, ya que el navegador no puede volver a intentarlo ni reanudarlo.
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 tuvo un estado no aceptable, p.ej., 404.
  • "fetch-error": Una de las recuperaciones falló por algún 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 valor de "downloadTotal" proporcionado.
recordsAvailable boolean
¿Se puede acceder a las solicitudes o respuestas subyacentes?

Una vez que sea falso, no se podrán usar match ni matchAll.

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

La promesa que se muestra se resuelve con el valor true si la recuperación se anuló con éxito.

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

Los argumentos aquí son los mismos que los de la API de la 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 indicó anteriormente, pero se resuelve con la primera coincidencia.
Eventos
progress Se activa cuando cambia alguno de los elementos uploaded, downloaded, result o failureReason.

Seguimiento del progreso

Esto se puede hacer a través del evento progress. Recuerda que downloadTotal es cualquier valor que hayas proporcionado o 0 si no proporcionaste ningún 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}%`);
});

Cómo obtener las solicitudes y 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á pendiente porque es posible que aún no se haya recibido. La promesa se rechazará si la recuperación falla.

Eventos de service worker

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

Esto solo es realmente útil si deseas limpiar datos relacionados.

backgroundfetchclick El usuario hizo clic en la IU del 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 estableciste inicialmente. Esto es opcional, pero te permite proporcionar más contexto si es necesario. Solo puedes hacer esto *una vez* durante los eventos backgroundfetchsuccess y backgroundfetchfailure.

Cómo reaccionar ante el éxito o el fracaso

Ya vimos el evento progress, pero solo es útil mientras el usuario tiene una página abierta en tu sitio. El principal beneficio de la actualización en segundo plano es que todo sigue funcionando después de que el usuario sale de la página o incluso cierra el navegador.

Si la actualización en segundo plano se completa correctamente, tu trabajador de servicio recibirá el evento backgroundfetchsuccess y event.registration será el registro de actualización en segundo plano.

Después de este evento, ya no se puede acceder a las solicitudes y respuestas recuperadas, por lo que, si deseas conservarlas, muévelas a algún lugar como la API de Cache.

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

Por ejemplo, en tu trabajador de servicio:

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 el error se deba a un solo error 404, que puede no haber sido importante para ti, por lo que podría ser útil copiar algunas respuestas en una caché como se indicó anteriormente.

Cómo reaccionar ante un clic

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

Lo más común que se hace con 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: Una versión anterior de este artículo hacía referencia de forma incorrecta a la actualización en segundo plano como un "estándar web". Actualmente, la API no está en el segmento de estándares. La especificación se puede encontrar en WICG como un informe de grupo comunitario en borrador.