Un caso d'uso concreto dei web worker

Nel modulo precedente, è stata fornita una panoramica dei web worker. I web worker possono migliorare la reattività all'input spostando JavaScript dal thread principale in thread dei web worker separati, il che può contribuire a migliorare l'interazione con il Next 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.

Uno di questi casi d'uso potrebbe essere un sito web che deve rimuovere i metadati Exif da un'immagine. Questo non è un concetto così inverosimile. Infatti, siti web come Flickr offrono agli utenti un modo per visualizzare i metadati EXIF per conoscere dettagli tecnici sulle immagini ospitate, come la profondità di colore, la marca e il modello della fotocamera e altri dati.

Tuttavia, la logica per il recupero di un'immagine, la sua conversione in un file ArrayBuffer e l'estrazione dei metadati Exif potrebbe essere potenzialmente costosa se eseguita interamente sul thread principale. Fortunatamente, l'ambito del web worker consente di svolgere questo lavoro dal thread principale. Quindi, utilizzando la pipeline di messaggistica del web worker, i metadati Exif vengono trasmessi al thread principale come stringa HTML e mostrati all'utente.

L'aspetto del thread principale senza un web worker

Innanzitutto, osserva l'aspetto del thread principale quando lavoriamo senza un web worker. Per eseguire questa operazione, procedi nel seguente modo:

  1. Apri una nuova scheda in Chrome e apri i relativi DevTools.
  2. Apri il riquadro sul rendimento.
  3. Passa a https://exif-worker.glitch.me/without-worker.html.
  4. Nel riquadro delle prestazioni, fai clic su Record (Registra) nell'angolo in alto a destra del riquadro DevTools.
  5. Incolla nel campo questo link immagine o un'altra a tua scelta contenente metadati EXIF e fai clic sul pulsante Scarica JPEG.
  6. Una volta completata l'interfaccia con i metadati EXIF, fai di nuovo clic su Record per interrompere la registrazione.
Il profiler delle prestazioni che mostra l'attività dell'app dell'estrattore di metadati delle immagini che si verifica interamente nel thread principale. Sono due le attività lunghe e sostanziali: una che esegue un recupero per ottenere l'immagine richiesta e la decodifica 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 verifica sul thread principale.

Tieni presente che, a parte altri thread che potrebbero essere presenti, come i thread dell'unità di rasterizzazione e così via, tutto ciò che si trova nell'app avviene sul thread principale. Nel thread principale, avviene 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 estratti per creare una stringa HTML che a sua volta compila il visualizzatore metadati.

Ora però, con l'implementazione dello stesso comportamento, ma usando un web worker.

Come si presenta il thread principale con un web worker

Ora che hai visto come si possono estrarre i metadati EXIF da un file JPEG sul thread principale, dai un'occhiata all'aspetto di un web worker:

  1. Apri un'altra scheda in Chrome e apri i relativi DevTools.
  2. Apri il riquadro sul rendimento.
  3. Vai a https://exif-worker.glitch.me/with-worker.html.
  4. Nel riquadro delle prestazioni, fai clic sul pulsante Registra nell'angolo in alto a destra del riquadro DevTools.
  5. Incolla questa immagine link nel campo e fai clic sul pulsante Ottieni quel JPEG.
  6. Una volta che l'interfaccia viene compilata con i metadati EXIF, fai di nuovo clic sul pulsante Registra per interrompere la registrazione.
Profiler delle prestazioni che mostra l'attività dell'app di estrazione dei metadati delle immagini che si verifica sia nel thread principale che in un thread di worker web. Anche se ci sono ancora attività lunghe nel thread principale, sono sostanzialmente più brevi, con il recupero/decodifica dell'immagine e l'estrazione dei metadati che avvengono interamente su un thread di web worker. L'unica attività del thread principale prevede il passaggio di dati da e verso il web worker.
Attività del thread principale nell'app di estrazione dei metadati delle immagini. Tieni presente che è presente un thread del worker web aggiuntivo in cui viene svolta la maggior parte del lavoro.

Questa è la forza di un web worker. Anziché eseguire tutte le operazioni nel thread principale, tutto tranne il completamento di HTML nel visualizzatore di metadati, viene eseguita in un thread separato. Ciò significa che il thread principale viene liberato per svolgere altre operazioni.

Forse il vantaggio più grande in questo caso è che, a differenza della versione di questa app che non utilizza un web worker, lo script exif-reader non viene caricato nel thread principale, ma piuttosto nel thread del worker web. Ciò significa che i costi di download, analisi e compilazione dello script exif-reader vengono effettuati al di fuori del thread principale.

Ora passiamo ad approfondire il codice dei web worker che rende tutto questo possibile.

Uno sguardo al codice del web worker

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

Inizia con il codice del thread principale che deve verificarsi prima che il web worker possa accedere all'immagine:

// 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 exif-reader esterno, quindi configura la pipeline di messaggistica sul 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 bit di JavaScript configura la pipeline di messaggistica in modo che, quando l'utente invia il modulo con un URL che rimanda a un file JPEG, che l'URL arrivi al web worker. Da qui, questo bit di codice successivo estrae i metadati EXIF dal file JPEG, crea una stringa HTML e invia il codice HTML a window per essere poi mostrato all'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://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('')
      });
    });
  });
});

È un po' da leggere, ma è anche un caso d'uso piuttosto coinvolto per i web worker. Tuttavia, i risultati valgono il lavoro e non solo in questo caso d'uso. Puoi utilizzare i web worker per qualsiasi cosa, ad esempio per isolare le chiamate fetch ed elaborare le risposte, elaborare grandi quantità di dati senza bloccare il thread principale. Questa pratica è solo per i principianti.

Quando migliori le prestazioni delle tue applicazioni web, inizia a pensare a qualsiasi cosa che possa essere ragionevolmente effettuata nel contesto di un web worker. I guadagni potrebbero essere significativi e possono portare a una migliore esperienza utente complessiva sul tuo sito web.