Présentation de la récupération en arrière-plan

Jake Archibld
Jake Archibald

En 2015, nous avons lancé la synchronisation en arrière-plan, qui permet au service worker de différer le travail de l'utilisateur jusqu'à ce qu'il se connecte. Cela signifie que l'utilisateur peut saisir un message, cliquer sur "Envoyer" et quitter le site en sachant que le message sera envoyé soit maintenant, soit lorsqu'il sera connecté à Internet.

Il s'agit d'une fonctionnalité utile, mais elle nécessite que le service worker soit actif pendant toute la durée de la récupération. Cela n'est pas un problème pour de courtes tâches comme l'envoi d'un message, mais si la tâche prend trop de temps, le navigateur tue le service worker. Dans le cas contraire, cela représente un risque pour la confidentialité et la batterie de l'utilisateur.

Alors, que se passe-t-il si vous avez besoin de télécharger quelque chose qui peut prendre beaucoup de temps, comme un film, des podcasts ou les niveaux d'un jeu ? C'est à cela que sert la récupération en arrière-plan.

La récupération en arrière-plan est disponible par défaut depuis Chrome 74.

Voici une démonstration rapide de deux minutes, qui vous présente l'état habituel des opérations par rapport à l'utilisation de la récupération en arrière-plan:

Testez vous-même la démonstration et parcourez le code.

Fonctionnement

La récupération en arrière-plan fonctionne comme suit:

  1. Vous demandez au navigateur d'effectuer un groupe d'extractions en arrière-plan.
  2. Le navigateur récupère ces éléments et affiche la progression.
  3. Une fois la récupération terminée ou échouée, le navigateur ouvre votre service worker et déclenche un événement pour vous informer de ce qui s'est passé. C'est ici que vous décidez quoi faire des réponses, le cas échéant.

Si l'utilisateur ferme des pages sur votre site après l'étape 1, il n'y a pas de problème, le téléchargement se poursuivra. Étant donné que la récupération est très visible et facilement annulable, une tâche de synchronisation en arrière-plan beaucoup trop longue n'est pas un problème de confidentialité. Comme le service worker n'est pas en cours d'exécution en continu, il n'est pas question d'abuser du système, par exemple pour miner des bitcoins en arrière-plan.

Sur certaines plates-formes (telles qu'Android), il est possible de fermer le navigateur après l'étape 1, car il peut transmettre l'extraction au système d'exploitation.

Si l'utilisateur lance le téléchargement en mode hors connexion, ou s'il est hors connexion pendant le téléchargement, l'extraction en arrière-plan est suspendue et reprend plus tard.

L'API

Détection de fonctionnalité

Comme pour toute nouvelle fonctionnalité, vous devez vérifier si le navigateur la prend en charge. Pour la récupération en arrière-plan, c'est aussi simple que:

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

Lancer une récupération en arrière-plan

L'API principale bloque l'enregistrement d'un service worker. Vous devez donc d'abord enregistrer un service worker. Ensuite :

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 comporte trois arguments:

Paramètres
id string
identifie de manière unique cette extraction en arrière-plan.

backgroundFetch.fetch sera refusé si l'ID correspond à une extraction en arrière-plan existante.

requests Array<Request|string>
Éléments à récupérer. Les chaînes seront traitées comme des URL et transformées en Request via new Request(theString).

Vous pouvez récupérer des éléments à partir d'autres origines, à condition que les ressources le permettent via CORS.

Remarque:Pour le moment, Chrome n'est pas compatible avec les requêtes qui nécessitent une requête CORS préliminaire.

options Un objet pouvant inclure les éléments suivants:
options.title string
Titre à afficher dans le navigateur avec la progression.
options.icons Array<IconDefinition>
Tableau d'objets avec "src", "size" et "type".
options.downloadTotal number
Taille totale des corps de la réponse (après extraction des fichiers gzip).

Bien que ce champ soit facultatif, nous vous recommandons vivement de le renseigner. Elle permet d'indiquer à l'utilisateur la taille du téléchargement et de fournir des informations sur la progression. Si vous ne le faites pas, le navigateur indiquera à l'utilisateur que la taille est inconnue. Il sera donc plus susceptible d'annuler le téléchargement.

Si le nombre de téléchargements de la récupération en arrière-plan dépasse le nombre indiqué ici, l'opération est annulée. Si la taille du téléchargement est inférieure à la valeur downloadTotal, ce n'est pas un problème. Si vous n'êtes pas sûr du total du téléchargement, il est donc préférable d'être prudent.

backgroundFetch.fetch renvoie une promesse qui se résout avec un élément BackgroundFetchRegistration. Je vous en parlerai les détails plus tard. La promesse est rejetée si l'utilisateur a désactivé les téléchargements ou si l'un des paramètres fournis n'est pas valide.

Fournir de nombreuses requêtes pour une seule extraction en arrière-plan vous permet de combiner des éléments logiquement comme un seul élément pour l'utilisateur. Par exemple, un film peut être divisé en milliers de ressources (généralement avec MPEG-DASH) et être accompagné de ressources supplémentaires telles que des images. Un niveau de jeu peut être réparti sur de nombreuses ressources JavaScript, image et audio. Mais pour l'utilisateur, c'est juste « le film », ou « le niveau ».

Obtenir une extraction en arrière-plan existante

Vous pouvez obtenir une extraction en arrière-plan existante comme ceci:

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

... en transmettant l'id de la récupération en arrière-plan souhaitée. get renvoie undefined s'il n'y a pas d'extraction en arrière-plan active avec cet ID.

Une extraction en arrière-plan est considérée comme "active" à partir du moment où elle est enregistrée, jusqu'à sa réussite, son échec ou son annulation.

Vous pouvez obtenir la liste de toutes les récupérations en arrière-plan actives à l'aide de getIds:

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

Inscriptions à la récupération en arrière-plan

Un BackgroundFetchRegistration (bgFetch dans les exemples ci-dessus) comporte les éléments suivants:

Propriétés
id string
ID de la récupération en arrière-plan.
uploadTotal number
Nombre d'octets à envoyer au serveur.
uploaded number
Nombre d'octets envoyés.
downloadTotal number
Valeur fournie lorsque la récupération en arrière-plan a été enregistrée, ou zéro.
downloaded number
Nombre d'octets reçus.

Cette valeur peut diminuer. Par exemple, si la connexion est interrompue et que le téléchargement ne peut pas être relancé, le navigateur redémarre alors la récupération de la ressource à partir de zéro.

result

Choisissez l'une des options suivantes :

  • "" : la récupération en arrière-plan est active. Il n'y a donc pas encore de résultat.
  • "success" : la récupération en arrière-plan a réussi.
  • "failure" : échec de la récupération en arrière-plan. Cette valeur n'apparaît que lorsque la récupération en arrière-plan échoue complètement, car le navigateur ne peut pas effectuer de nouvelle tentative/reprise.
failureReason

Choisissez l'une des options suivantes :

  • "" : la récupération en arrière-plan n'a pas échoué.
  • "aborted" : la récupération en arrière-plan a été annulée par l'utilisateur, ou abort() a été appelé.
  • "bad-status" : l'une des réponses indiquait un état incorrect (par exemple, 404).
  • "fetch-error" : l'une des récupérations a échoué pour une autre raison, par exemple CORS ou MIX, une réponse partielle non valide ou un échec général du réseau pour une récupération qui ne peut pas être relancée.
  • "quota-exceeded" : le quota de stockage a été atteint pendant la récupération en arrière-plan.
  • "download-total-exceeded" : le "downloadTotal" fourni a été dépassé.
recordsAvailable boolean
Les requêtes/réponses sous-jacentes sont-elles accessibles ?

Une fois la valeur définie sur "false", match et matchAll ne peuvent plus être utilisés.

Méthodes
abort() Renvoie Promise<boolean>
Annule la récupération en arrière-plan.

La promesse renvoyée est résolue avec "true" si la récupération a été annulée avec succès.

matchAll(request, opts) Renvoie Promise<Array<BackgroundFetchRecord>>
Obtenez les requêtes et les réponses.

Les arguments sont les mêmes que pour l'API de cache. Un appel sans arguments renvoie une promesse pour tous les enregistrements.

Pour en savoir plus, reportez-vous aux informations ci-dessous.

match(request, opts) Renvoie Promise<BackgroundFetchRecord>
Comme ci-dessus, mais se résout avec la première correspondance.
Événements
progress Déclenché lorsque uploaded, downloaded, result ou failureReason change.

Suivre la progression

Pour ce faire, utilisez l'événement progress. N'oubliez pas que downloadTotal correspond à la valeur que vous avez fournie ou à 0 si vous n'en avez pas fourni.

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

Obtenir les requêtes et les réponses

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 est de type BackgroundFetchRecord, et se présente comme suit:

Propriétés
request Request
Demande fournie.
responseReady Promise<Response>
Réponse récupérée.

La réponse est derrière une promesse, car elle n'a peut-être pas encore été reçue. La promesse est refusée si la récupération échoue.

Événements de service worker

Événements
backgroundfetchsuccess Toutes les données ont bien été récupérées.
backgroundfetchfailure Échec d'une ou de plusieurs récupérations.
backgroundfetchabort Échec d'une ou de plusieurs récupérations.

Ceci n'est vraiment utile que si vous souhaitez effectuer un nettoyage de données associées.

backgroundfetchclick L'utilisateur a cliqué sur l'interface utilisateur de progression du téléchargement.

Les objets événement se présentent comme suit:

Propriétés
registration BackgroundFetchRegistration
Méthodes
updateUI({ title, icons }) permet de modifier le titre ou les icônes que vous avez définis initialement. Cette étape est facultative, mais elle vous permet de fournir plus de contexte si nécessaire. Vous ne pouvez effectuer cette opération qu'*une fois* pendant les événements backgroundfetchsuccess et backgroundfetchfailure.

Réagir à la réussite ou à l'échec

Nous avons déjà vu l'événement progress, mais il n'est utile que lorsque l'utilisateur a une page ouverte sur votre site. Le principal avantage de la récupération en arrière-plan est qu'elle continue de fonctionner une fois que l'utilisateur a quitté la page, voire qu'il ferme le navigateur.

Si l'extraction en arrière-plan aboutit, votre service worker reçoit l'événement backgroundfetchsuccess, et event.registration est l'enregistrement de la récupération en arrière-plan.

Après cet événement, les requêtes et les réponses récupérées ne sont plus accessibles. Si vous souhaitez les conserver, déplacez-les par exemple vers l'API de cache.

Comme pour la plupart des événements de service worker, utilisez event.waitUntil pour qu'il sache quand l'événement est terminé.

Par exemple, dans votre 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!' });
  }());
});

L'échec peut être dû à une seule erreur 404, ce qui n'était peut-être pas important pour vous. Il peut donc toujours être utile de copier certaines réponses dans un cache, comme ci-dessus.

Réagir au clic

UI affichant la progression du téléchargement et le résultat est cliquable L'événement backgroundfetchclick dans le service worker vous permet de réagir à cette action. Comme ci-dessus, event.registration correspond à l'enregistrement de la récupération en arrière-plan.

Cet événement consiste généralement à ouvrir une fenêtre:

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

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

Ressources supplémentaires

Correction: dans une précédente version de cet article, la récupération en arrière-plan était considérée à tort comme une "norme Web". L'API n'est pas sur le canal pour le moment. La spécification est disponible dans le WICG en tant que brouillon du rapport du groupe de la communauté.