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

Jake Archibald
Jake Archibald

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

Il s'agit d'une fonctionnalité utile, mais elle nécessite que le service worker soit actif pendant toute la durée de l'extraction. Ce n'est pas un problème pour les tâches courtes, comme l'envoi d'un message, mais si la tâche prend trop de temps, le navigateur arrête le service worker, sinon il risque de compromettre la confidentialité et la batterie de l'utilisateur.

Que faire si vous devez télécharger un élément qui peut prendre beaucoup de temps, comme un film, des podcasts ou des 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 courte démonstration de deux minutes montrant l'état traditionnel des choses par rapport à l'utilisation de la récupération en arrière-plan:

Essayez la démo vous-même 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 de récupérations en arrière-plan.
  2. Le navigateur récupère ces éléments et affiche la progression à l'utilisateur.
  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 indiquer ce qui s'est passé. C'est là que vous décidez de ce que vous voulez faire des réponses, le cas échéant.

Si l'utilisateur ferme les pages de votre site après l'étape 1, ce n'est pas grave, le téléchargement continue. Étant donné que la récupération est très visible et facilement interrompue, il n'y a pas de problème de confidentialité lié à une tâche de synchronisation en arrière-plan beaucoup trop longue. Comme le service worker n'est pas exécuté en permanence, il n'y a pas de risque qu'il abuse du système, par exemple en minant des bitcoins en arrière-plan.

Sur certaines plates-formes (telles qu'Android), le navigateur peut se fermer après l'étape 1, car il peut transférer la récupération au système d'exploitation.

Si l'utilisateur lance le téléchargement en mode hors connexion ou se déconnecte pendant le téléchargement, la récupération en arrière-plan est suspendue et reprise ultérieurement.

L'API

Détection de caractéristiques

Comme pour toute nouvelle fonctionnalité, vous devez vérifier si le navigateur est compatible avec celle-ci. Pour la récupération en arrière-plan, il suffit de:

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

Démarrer une récupération en arrière-plan

L'API principale dépend de l'enregistrement d'un service worker. Assurez-vous donc d'avoir d'abord enregistré 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 utilise trois arguments:

Paramètres
id string
identifie de manière unique cette récupération en arrière-plan.

backgroundFetch.fetch rejette les requêtes si l'ID correspond à une extraction en arrière-plan existante.

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

Vous pouvez extraire des éléments à partir d'autres origines tant que les ressources le permettent via le CORS.

Remarque:Chrome n'est actuellement pas compatible avec les requêtes nécessitant une vérification préliminaire CORS.

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

Bien que cette information soit facultative, nous vous recommandons vivement de la fournir. Il permet d'indiquer à l'utilisateur la taille du téléchargement et de fournir des informations sur la progression. Si vous ne fournissez pas cette information, le navigateur indique à l'utilisateur que la taille est inconnue. Il est donc plus susceptible d'abandonner le téléchargement.

Si les téléchargements de récupération en arrière-plan dépassent le nombre indiqué ici, ils seront interrompus. Il est tout à fait acceptable que le téléchargement soit inférieur à downloadTotal. Par conséquent, si vous n'êtes pas sûr de la taille totale du téléchargement, mieux vaut être prudent.

backgroundFetch.fetch renvoie une promesse qui se résout avec un BackgroundFetchRegistration. Je vous en parlerai en détail 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 récupération en arrière-plan vous permet de combiner des éléments qui sont logiquement une seule chose pour l'utilisateur. Par exemple, un film peut être divisé en milliers de ressources (typique avec MPEG-DASH) et être accompagné de ressources supplémentaires telles que des images. Un niveau de jeu peut être réparti entre de nombreuses ressources JavaScript, image et audio. Mais pour l'utilisateur, il s'agit simplement du "film" ou du "niveau".

Obtenir une récupération en arrière-plan existante

Vous pouvez récupérer 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 l'extraction en arrière-plan souhaitée. get renvoie undefined si aucune récupération en arrière-plan active n'est associée à cet ID.

Une récupération en arrière-plan est considérée comme "active" à partir du moment où elle est enregistrée, jusqu'à ce qu'elle aboutisse, échoue ou soit interrompue.

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();
});

Enregistrements de récupération en arrière-plan

Un BackgroundFetchRegistration (bgFetch dans les exemples ci-dessus) possède 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 avec succès.
downloadTotal number
Valeur fournie lors de l'enregistrement de la récupération en arrière-plan, 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 repris, le navigateur redémarre la récupération de cette 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 de l'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 réessayer ni reprendre.
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é interrompue par l'utilisateur ou abort() a été appelé.
  • "bad-status" : une des réponses affichait un état incorrect (404, par exemple).
  • "fetch-error" : l'une des récupérations a échoué pour une autre raison, par exemple CORS, MIX, une réponse partielle non valide ou une panne réseau générale pour une récupération qui ne peut pas être réessayée.
  • "quota-exceeded" : le quota de stockage a été atteint lors de la récupération en arrière-plan.
  • "download-total-exceeded" : la valeur "downloadTotal" fournie a été dépassée.
recordsAvailable boolean
Les requêtes/réponses sous-jacentes sont-elles accessibles ?

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

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

La promesse renvoyée aboutit à "true" si l'extraction a bien été interrompue.

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

Les arguments ici sont les mêmes que ceux de 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 avec la première correspondance.
Événements
progress Déclenché lorsque uploaded, downloaded, result ou failureReason sont modifiés.

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 un BackgroundFetchRecord et se présente comme suit:

Propriétés
request Request
Requête 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 sera rejeté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 L'extraction d'un ou de plusieurs éléments a échoué.
backgroundfetchabort Échec d'une ou plusieurs récupérations.

Cette option n'est vraiment utile que si vous souhaitez nettoyer les données associées.

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

Les objets d'événement présentent les caractéristiques suivantes:

Propriétés
registration BackgroundFetchRegistration
Méthodes
updateUI({ title, icons }) Vous permet de modifier le titre/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 seule fois* lors des événements backgroundfetchsuccess et backgroundfetchfailure.

Réagir en cas de réussite ou d'é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. L'avantage principal de la récupération en arrière-plan est que les éléments continuent de fonctionner après que l'utilisateur a quitté la page ou même fermé le navigateur.

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

Après cet événement, les requêtes et les réponses extraites ne sont plus accessibles. Si vous souhaitez les conserver, déplacez-les vers un emplacement tel que l'API de cache.

Comme pour la plupart des événements du service worker, utilisez event.waitUntil pour que le service worker 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û à un seul code 404, qui n'était peut-être pas important pour vous. Il peut donc être utile de copier certaines réponses dans un cache, comme ci-dessus.

Réaction au clic

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

La chose courante à faire avec cet événement est d'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: Une version précédente de cet article faisait référence à la récupération en arrière-plan comme étant une "norme Web". L'API n'est pas actuellement dans la voie de normalisation. La spécification est disponible dans le WICG sous la forme d'un projet de rapport du groupe de la communauté.