Un cas d'utilisation concret d'un travailleur Web

Dans le dernier module, nous avons présenté les Web Workers. Les Web Workers peuvent améliorer la réactivité des entrées en déplaçant le code JavaScript du thread principal vers des threads Web Worker distincts. Cela peut contribuer à améliorer l'interaction avec le prochain affichage (INP) de votre site Web lorsque vous avez du travail qui n'a pas besoin d'accéder directement au thread principal. Toutefois, un aperçu seul ne suffit pas. Ce module propose un cas d'utilisation concret pour un Web Worker.

Un cas d'utilisation pourrait être un site Web qui doit supprimer les métadonnées Exif d'une image. Ce n'est pas un concept si farfelu. En effet, des sites Web comme Flickr permettent aux utilisateurs de consulter les métadonnées Exif pour en savoir plus sur les caractéristiques techniques des images qu'ils hébergent, comme la profondeur de couleur, la marque et le modèle de l'appareil photo, et d'autres données.

Toutefois, la logique pour récupérer une image, la convertir en ArrayBuffer et extraire les métadonnées Exif peut être coûteuse si elle est effectuée entièrement sur le thread principal. Heureusement, le champ d'application du Web Worker permet d'effectuer ce travail en dehors du thread principal. Ensuite, à l'aide du canal de messagerie du Web Worker, les métadonnées Exif sont renvoyées au thread principal sous forme de chaîne HTML et affichées à l'utilisateur.

À quoi ressemble le thread principal sans nœud de calcul Web

Tout d'abord, observons à quoi ressemble le thread principal lorsque nous effectuons ce travail sans Web Worker. Pour ce faire, procédez comme suit :

  1. Ouvrez un nouvel onglet dans Chrome, puis ouvrez ses outils pour les développeurs.
  2. Ouvrez le panneau des performances.
  3. Accédez à https://chrome.dev/learn-performance-exif-worker/without-worker.html.
  4. Dans le panneau "Performances", cliquez sur Enregistrer en haut à droite du volet Outils pour les développeurs.
  5. Collez ce lien d'image (ou un autre de votre choix contenant des métadonnées Exif) dans le champ, puis cliquez sur le bouton Get that JPEG! (Récupérer ce JPEG !).
  6. Une fois que l'interface est remplie avec les métadonnées Exif, cliquez à nouveau sur Enregistrer pour arrêter l'enregistrement.
Le profileur de performances montre que l'activité de l'application d'extraction des métadonnées d'image se déroule entièrement sur le thread principal. Il existe deux tâches longues importantes : l'une exécute une récupération pour obtenir l'image demandée et la décoder, et l'autre extrait les métadonnées de l'image.
Activité du thread principal dans l'application d'extraction des métadonnées d'image. Notez que toute l'activité se produit sur le thread principal.

Notez que, mis à part les autres threads qui peuvent être présents (tels que les threads de rastérisation, etc.), tout ce qui se passe dans l'application se produit sur le thread principal. Sur le thread principal, voici ce qui se passe :

  1. Le formulaire prend l'entrée et envoie une requête fetch pour obtenir la partie initiale de l'image contenant les métadonnées Exif.
  2. Les données d'image sont converties en ArrayBuffer.
  3. Le script exif-reader permet d'extraire les métadonnées Exif de l'image.
  4. Les métadonnées sont extraites pour construire une chaîne HTML, qui remplit ensuite le lecteur de métadonnées.

Comparons cela à une implémentation du même comportement, mais en utilisant un Web Worker.

À quoi ressemble le thread principal avec un Web Worker

Maintenant que vous avez vu à quoi ressemble l'extraction des métadonnées Exif d'un fichier JPEG sur le thread principal, regardez à quoi cela ressemble lorsqu'un Web Worker est impliqué :

  1. Ouvrez un autre onglet dans Chrome, puis ouvrez ses outils pour les développeurs.
  2. Ouvrez le panneau des performances.
  3. Accédez à https://chrome.dev/learn-performance-exif-worker/with-worker.html.
  4. Dans le panneau "Performances", cliquez sur le bouton d'enregistrement en haut à droite du panneau des outils de développement.
  5. Collez ce lien d'image dans le champ, puis cliquez sur le bouton Get that JPEG! (Obtenir ce JPEG !).
  6. Une fois que l'interface est remplie avec les métadonnées Exif, cliquez à nouveau sur le bouton d'enregistrement pour arrêter l'enregistrement.
Le profileur de performances affichant l'activité de l'application d'extraction des métadonnées d'image se produisant à la fois sur le thread principal et sur un thread de worker Web. Bien qu'il y ait encore des tâches longues sur le thread principal, elles sont beaucoup plus courtes, l'extraction/le décodage des images et l'extraction des métadonnées se produisant entièrement sur un thread de nœud de calcul Web. Le seul travail du thread principal consiste à transmettre des données au nœud de calcul Web et à en recevoir.
Activité du thread principal dans l'application d'extraction des métadonnées d'image. Notez qu'il existe un thread de calcul Web supplémentaire où la plupart des tâches sont effectuées.

C'est là tout le pouvoir d'un worker Web. Au lieu de tout faire sur le thread principal, tout ce qui n'est pas lié au remplissage du lecteur de métadonnées avec du code HTML est effectué sur un thread distinct. Cela signifie que le thread principal est libéré pour effectuer d'autres tâches.

L'avantage principal ici est que, contrairement à la version de cette application qui n'utilise pas de Web Worker, le script exif-reader n'est pas chargé sur le thread principal, mais plutôt sur le thread Web Worker. Cela signifie que le coût du téléchargement, de l'analyse et de la compilation du script exif-reader a lieu en dehors du thread principal.

Passons maintenant au code du Web Worker qui rend tout cela possible.

Examen du code du nœud de calcul Web

Il ne suffit pas de voir la différence qu'un Web Worker apporte. Il est également utile de comprendre, au moins dans ce cas, à quoi ressemble ce code pour savoir ce qui est possible dans le champ d'application du Web Worker.

Commencez par le code du thread principal qui doit se produire avant que le nœud de calcul Web puisse entrer en jeu :

// scripts.js

// Register the Exif reader web worker:
const exifWorker = new Worker('/js/with-worker/exif-worker.js');

// We have to send image requests through this proxy due to CORS limitations:
const imageFetchPrefix = 'https://res.cloudinary.com/demo/image/fetch/';

// Necessary elements we need to select:
const imageFetchPanel = document.getElementById('image-fetch');
const imageExifDataPanel = document.getElementById('image-exif-data');
const exifDataPanel = document.getElementById('exif-data');
const imageInput = document.getElementById('image-url');

// What to do when the form is submitted.
document.getElementById('image-form').addEventListener('submit', event => {
  // Don't let the form submit by default:
  event.preventDefault();

  // Send the image URL to the web worker on submit:
  exifWorker.postMessage(`${imageFetchPrefix}${imageInput.value}`);
});

// This listens for the Exif metadata to come back from the web worker:
exifWorker.addEventListener('message', ({ data }) => {
  // This populates the Exif metadata viewer:
  exifDataPanel.innerHTML = data.message;
  imageFetchPanel.style.display = 'none';
  imageExifDataPanel.style.display = 'block';
});

Ce code s'exécute sur le thread principal et configure le formulaire pour envoyer l'URL de l'image au nœud de calcul Web. À partir de là, le code du Web Worker commence par une instruction importScripts qui charge le script exif-reader externe, puis configure le pipeline de messagerie vers le thread principal :

// exif-worker.js

// Import the exif-reader script:
importScripts('/js/with-worker/exifreader.js');

// Set up a messaging pipeline to send the Exif data to the `window`:
self.addEventListener('message', ({ data }) => {
  getExifDataFromImage(data).then(status => {
    self.postMessage(status);
  });
});

Ce code JavaScript configure le pipeline de messagerie de sorte que, lorsque l'utilisateur envoie le formulaire avec l'URL d'un fichier JPEG, l'URL arrive dans le Web Worker. À partir de là, la partie suivante du code extrait les métadonnées Exif du fichier JPEG, crée une chaîne HTML et renvoie ce code HTML à window pour qu'il soit finalement affiché à l'utilisateur :

// Takes a blob to transform the image data into an `ArrayBuffer`:
// NOTE: these promises are simplified for readability, and don't include
// rejections on failures. Check out the complete web worker code:
// https://chrome.dev/learn-performance-exif-worker/js/with-worker/exif-worker.js
const readBlobAsArrayBuffer = blob => new Promise(resolve => {
  const reader = new FileReader();

  reader.onload = () => {
    resolve(reader.result);
  };

  reader.readAsArrayBuffer(blob);
});

// Takes the Exif metadata and converts it to a markup string to
// display in the Exif metadata viewer in the DOM:
const exifToMarkup = exif => Object.entries(exif).map(([exifNode, exifData]) => {
  return `
    <details>
      <summary>
        <h2>${exifNode}</h2>
      </summary>
      <p>${exifNode === 'base64' ? `<img src="data:image/jpeg;base64,${exifData}">` : typeof exifData.value === 'undefined' ? exifData : exifData.description || exifData.value}</p>
    </details>
  `;
}).join('');

// Fetches a partial image and gets its Exif data
const getExifDataFromImage = imageUrl => new Promise(resolve => {
  fetch(imageUrl, {
    headers: {
      // Use a range request to only download the first 64 KiB of an image.
      // This ensures bandwidth isn't wasted by downloading what may be a huge
      // JPEG file when all that's needed is the metadata.
      'Range': `bytes=0-${2 ** 10 * 64}`
    }
  }).then(response => {
    if (response.ok) {
      return response.clone().blob();
    }
  }).then(responseBlob => {
    readBlobAsArrayBuffer(responseBlob).then(arrayBuffer => {
      const tags = ExifReader.load(arrayBuffer, {
        expanded: true
      });

      resolve({
        status: true,
        message: Object.values(tags).map(tag => exifToMarkup(tag)).join('')
      });
    });
  });
});

Il y a pas mal de choses à lire, mais il s'agit également d'un cas d'utilisation assez complexe pour les Web Workers. Cependant, les résultats valent la peine, et pas seulement pour ce cas d'utilisation. Vous pouvez utiliser des workers Web pour toutes sortes de choses, comme isoler les appels fetch et traiter les réponses, traiter de grandes quantités de données sans bloquer le thread principal, et ce n'est que le début.

Lorsque vous améliorez les performances de vos applications Web, commencez par réfléchir à tout ce qui peut être fait de manière raisonnable dans un contexte de nœud de calcul Web. Les gains peuvent être importants et améliorer l'expérience utilisateur globale sur votre site Web.