Un caso d'uso concreto dei web worker

Nell'ultimo modulo è stata fornita una panoramica dei web worker. I web worker possono migliorare la reattività dell'input spostando JavaScript dal thread principale a thread web worker separati, il che può contribuire a migliorare l'interazione con il successivo paint (INP) del tuo sito web quando hai un lavoro che non richiede l'accesso diretto al thread principale. Tuttavia, una panoramica da sola non è sufficiente e in questo modulo viene offerto un caso d'uso concreto per un web worker.

Un caso d'uso potrebbe essere un sito web che deve rimuovere i metadati Exif da un'immagine, un concetto non così inverosimile. Infatti, siti web come Flickr offrono agli utenti un modo per visualizzare i metadati Exif per conoscere i dettagli tecnici delle immagini che ospitano, come la profondità del colore, la marca e il modello della fotocamera e altri dati.

Tuttavia, la logica per recuperare un'immagine, convertirla in un ArrayBuffer ed estrarre i metadati Exif potrebbe essere potenzialmente costosa se eseguita interamente sul thread principale. Fortunatamente, l'ambito del web worker consente di eseguire questa operazione al di fuori del thread principale. Quindi, utilizzando la pipeline di messaggistica del web worker, i metadati Exif vengono ritrasmessi al thread principale come stringa HTML e visualizzati per l'utente.

Aspetto del thread principale senza un web worker

Innanzitutto, osserva l'aspetto del thread principale quando eseguiamo questo lavoro senza un web worker. Per farlo, segui questi passaggi:

  1. Apri una nuova scheda in Chrome e apri DevTools.
  2. Apri il riquadro del rendimento.
  3. Vai alla pagina https://chrome.dev/learn-performance-exif-worker/without-worker.html.
  4. Nel riquadro del rendimento, fai clic su Registra nell'angolo in alto a destra del riquadro DevTools.
  5. Incolla questo link all'immagine o un altro a tua scelta che contenga metadati Exif nel campo e fai clic sul pulsante Scarica il JPEG.
  6. Una volta che l'interfaccia viene compilata con i metadati Exif, fai di nuovo clic su Registra per interrompere la registrazione.
Il profiler delle prestazioni che mostra l'attività dell'app di estrazione dei metadati delle immagini che si svolge interamente sul thread principale. Esistono due attività lunghe sostanziali: una che esegue un recupero per ottenere l'immagine richiesta e decodificarla e un'altra che estrae i metadati dall'immagine.
Attività del thread principale nell'app di estrazione dei metadati delle immagini. Tieni presente che tutta l'attività si svolge sul thread principale.

Tieni presente che, a parte altri thread che potrebbero essere presenti, come i thread rasterizzatori e così via, tutto ciò che riguarda l'app si verifica nel thread principale. Nel thread principale, si verifica quanto segue:

  1. Il modulo prende l'input e invia una richiesta fetch per ottenere la porzione iniziale dell'immagine contenente i metadati Exif.
  2. I dati dell'immagine vengono convertiti in un ArrayBuffer.
  3. Lo script exif-reader viene utilizzato per estrarre i metadati Exif dall'immagine.
  4. I metadati vengono sottoposti a scraping per creare una stringa HTML, che viene poi inserita nel visualizzatore di metadati.

Ora confronta questo comportamento con un'implementazione dello stesso comportamento, ma utilizzando un web worker.

Aspetto del thread principale con un web worker

Ora che hai visto come estrarre i metadati Exif da un file JPEG nel thread principale, dai un'occhiata a come appare quando è coinvolto un web worker:

  1. Apri un'altra scheda in Chrome e apri DevTools.
  2. Apri il riquadro del rendimento.
  3. Vai alla pagina https://chrome.dev/learn-performance-exif-worker/with-worker.html.
  4. Nel riquadro del rendimento, fai clic sul pulsante di registrazione nell'angolo in alto a destra del riquadro DevTools.
  5. Incolla questo link all'immagine nel campo e fai clic sul pulsante Scarica il JPEG.
  6. Una volta che l'interfaccia viene compilata con i metadati Exif, fai di nuovo clic sul pulsante di registrazione per interrompere la registrazione.
Il profiler delle prestazioni che mostra l'attività dell'app di estrazione dei metadati delle immagini che si verifica sia nel thread principale sia in un thread web worker. Sebbene siano ancora presenti attività lunghe nel thread principale, queste sono notevolmente più brevi, con il recupero/decodifica delle immagini e l'estrazione dei metadati che avvengono interamente in un thread web worker. L'unico lavoro del thread principale consiste nel passare i dati al web worker e viceversa.
Attività del thread principale nell'app di estrazione dei metadati delle immagini. Tieni presente che esiste un thread di web worker aggiuntivo in cui viene svolta la maggior parte del lavoro.

Questo è il potere di un web worker. Anziché eseguire tutto sul thread principale, tutto tranne il riempimento del visualizzatore di metadati con HTML viene eseguito su un thread separato. Ciò significa che il thread principale è libero di svolgere altre attività.

Il vantaggio principale è che, a differenza della versione di questa app che non utilizza un web worker, lo script exif-reader non viene caricato sul thread principale, ma sul thread del web worker. Ciò significa che il costo di download, analisi e compilazione dello script exif-reader avviene al di fuori del thread principale.

Ora passiamo al codice del web worker che rende tutto questo possibile.

Uno sguardo al codice del web worker

Non è sufficiente vedere la differenza che fa un web worker, ma è anche utile capire, almeno in questo caso, l'aspetto del codice per sapere cosa è possibile nell'ambito del web worker.

Inizia con il codice del thread principale che deve essere eseguito prima che il web worker possa entrare in gioco:

// 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';
});

Questo codice viene eseguito sul thread principale e configura il modulo per inviare l'URL dell'immagine al web worker. Da qui, il codice del web worker inizia con un'istruzione importScripts che carica lo script esterno exif-reader e poi configura la pipeline di messaggistica al thread principale:

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

Questo snippet di JavaScript configura la pipeline di messaggistica in modo che, quando l'utente invia il modulo con un URL a un file JPEG, l'URL arrivi nel web worker. Da qui, questo frammento di codice estrae i metadati Exif dal file JPEG, crea una stringa HTML e la invia di nuovo a window per essere visualizzata dall'utente:

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

È un po' da leggere, ma questo è anche un caso d'uso piuttosto complesso per i web worker. Tuttavia, i risultati valgono la pena e non si limitano a questo caso d'uso. Puoi utilizzare i web worker per svariate attività, ad esempio isolare le chiamate fetch ed elaborare le risposte, elaborare grandi quantità di dati senza bloccare il thread principale e questo è solo l'inizio.

Quando migliori il rendimento delle tue applicazioni web, inizia a pensare a tutto ciò che può essere ragionevolmente fatto nel contesto di un web worker. I vantaggi potrebbero essere significativi e possono portare a un'esperienza utente complessivamente migliore per il tuo sito web.