Un cas d'utilisation concret d'un travailleur Web

Dans le dernier module, nous avons présenté une présentation des workers Web. Les nœuds de calcul Web peuvent améliorer la réactivité aux entrées en déplaçant JavaScript depuis le thread principal vers des threads de nœuds de calcul Web distincts. Cela peut contribuer à améliorer l'interaction avec Next Paint (INP) lorsque certaines de vos tâches ne nécessitent pas d'accès direct au thread principal. Cependant, une présentation à elle seule ne suffit pas. Dans ce module, un cas d'utilisation concret est proposé pour un worker Web.

Un tel cas d'utilisation pourrait être un site Web qui doit supprimer les métadonnées EXIF d'une image. Ce concept n'est pas si farfelu. En fait, des sites Web tels que Flickr offrent aux utilisateurs un moyen d'afficher les métadonnées Exif afin d'obtenir des détails techniques sur les images qu'ils hébergent, telles que la profondeur des couleurs, la marque et le modèle de l'appareil photo, ainsi que d'autres données.

Cependant, la logique permettant d'extraire une image, de la convertir en ArrayBuffer et d'extraire les métadonnées Exif peut s'avérer coûteuse si elle est effectuée entièrement sur le thread principal. Heureusement, le champ d'application des nœuds de calcul Web permet d'effectuer ce travail en dehors du thread principal. Ensuite, à l'aide du pipeline de messagerie du nœud de calcul Web, les métadonnées Exif sont transmises au thread principal sous forme de chaîne HTML et présentées à l'utilisateur.

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

Tout d'abord, observez à quoi ressemble le thread principal lorsque nous effectuons ce travail sans nœud de calcul Web. Pour ce faire, procédez comme suit :

  1. Ouvrez un nouvel onglet dans Chrome, puis ouvrez ses outils de développement.
  2. Ouvrez le panneau des performances.
  3. Accédez à https://exif-worker.glitch.me/without-worker.html.
  4. Dans le panneau des performances, cliquez sur Record (Enregistrer) en haut à droite du volet des outils de développement.
  5. Collez ce lien image ou un autre de votre choix contenant des métadonnées Exif dans le champ, puis cliquez sur le bouton Get this JPEG (Obtenir ce JPEG).
  6. Une fois que l'interface contient les métadonnées Exif, cliquez à nouveau sur Record (Enregistrer) pour arrêter l'enregistrement.
Profileur de performances affichant l'activité de l'application d'extracteur de métadonnées d'image se déroulant entièrement sur le thread principal. Il y a deux tâches importantes et longues : l'une qui exécute une extraction pour obtenir l'image demandée et la décoder, et l'autre qui extrait les métadonnées de l'image.
Activité du thread principal dans l'application d'extracteur de métadonnées d'image. Notez que toutes les activités se produisent sur le thread principal.

Notez que, à l'exception d'autres threads éventuellement présents, tels que les threads de rastériseur, etc., tout ce qui se passe dans l'application se produit sur le thread principal. Sur le thread principal, voici ce qui se produit:

  1. Le formulaire reçoit 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 de l'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 récupérées pour construire une chaîne HTML, qui est ensuite insérée dans la visionneuse de métadonnées.

Comparons maintenant cette implémentation avec une implémentation du même comportement, mais à l'aide d'un worker Web.

À quoi ressemble le thread principal avec un worker Web

Maintenant que vous avez vu comment extraire les métadonnées Exif d'un fichier JPEG sur le thread principal, regardez à quoi cela ressemble lorsqu'un worker Web est dans le mix:

  1. Ouvrez un autre onglet dans Chrome et ouvrez les outils de développement.
  2. Ouvrez le panneau des performances.
  3. Accédez à https://exif-worker.glitch.me/with-worker.html.
  4. Dans le panneau des performances, cliquez sur le bouton d'enregistrement en haut à droite du volet des outils de développement.
  5. Collez ce lien image dans le champ, puis cliquez sur le bouton Get this JPEG! (Obtenir ce JPEG).
  6. Une fois que l'interface contient les métadonnées Exif, cliquez à nouveau sur le bouton d'enregistrement pour arrêter l'enregistrement.
Profileur de performances affichant l'activité de l'application d'extracteur de métadonnées d'image qui se produit à la fois sur le thread principal et sur un thread de nœud de calcul Web. Bien qu'il y ait encore de longues tâches sur le thread principal, elles sont beaucoup plus courtes, avec l'extraction/le décodage des images et l'extraction des métadonnées se déroulant entièrement sur un thread de nœud de calcul Web. La seule opération de thread principal implique la transmission de données vers et depuis le worker Web.
Activité du thread principal dans l'application d'extraction de métadonnées d'image. Notez qu'il existe un thread de nœud de calcul Web supplémentaire dans lequel la majeure partie du travail est effectuée.

C'est la puissance d'un web worker. Plutôt que d'effectuer toutes les opérations sur le thread principal, toutes les opérations sauf le remplissage de la visionneuse de métadonnées avec du code HTML s'effectue dans un thread distinct. Cela signifie que le thread principal est libéré pour effectuer d'autres tâches.

Le principal avantage de cette application est sans doute que, contrairement à la version de cette application qui n'utilise pas de nœud de calcul Web, le script exif-reader n'est pas chargé sur le thread principal, mais plutôt sur le thread de nœud de calcul Web. Cela signifie que le coût de téléchargement, d'analyse et de compilation du script exif-reader est effectué en dehors du thread principal.

Intéressons-nous maintenant au code de Web worker qui rend tout cela possible.

Examiner le code d'un nœud de calcul Web

Il ne suffit pas de voir la différence entre un nœud de calcul Web. Il est également utile de comprendre, du moins dans le cas présent, à quoi ressemble ce code, afin de savoir ce qui est possible dans le champ d'application d'un nœud de calcul Web.

Commencez par le code du thread principal qui doit apparaître avant que le nœud de calcul Web puisse accéder à l'image:

// 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. Ensuite, le code du nœud de calcul Web commence par une instruction importScripts qui charge le script externe exif-reader, puis configure le pipeline de messagerie sur 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 bit de JavaScript configure le pipeline de messagerie de sorte que lorsque l'utilisateur envoie le formulaire avec une URL vers un fichier JPEG, l'URL arrive au worker Web. À partir de là, le code suivant 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 présenté à 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://glitch.com/edit/#!/exif-worker?path=js%2Fwith-worker%2Fexif-worker.js%3A10%3A5
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('')
      });
    });
  });
});

C'est un peu à lire, mais c'est aussi un cas d'utilisation assez complexe pour les workers Web. Cependant, les résultats en valent la peine et ne se limitent pas à ce cas d'utilisation. Vous pouvez utiliser des nœuds de calcul Web pour toutes sortes de choses, telles que l'isolation des appels fetch et le traitement des réponses, le traitement de grandes quantités de données sans bloquer le thread principal, et ceci uniquement pour les déclencheurs.

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