Konkreter Anwendungsfall für Web Worker

Im letzten Modul haben Sie einen Überblick über Web Worker erhalten. Web Worker können die Reaktionszeit von Eingaben verbessern, indem sie JavaScript aus dem Hauptthread in separate Web Worker-Threads verschieben. Dadurch lässt sich die Interaction to Next Paint (INP) Ihrer Website verbessern, wenn Sie Arbeiten haben, die keinen direkten Zugriff auf den Hauptthread benötigen. Eine Übersicht allein reicht jedoch nicht aus. In diesem Modul wird ein konkreter Anwendungsfall für einen Web Worker vorgestellt.

Ein solcher Anwendungsfall könnte eine Website sein, bei der Exif-Metadaten aus einem Bild entfernt werden müssen. Das ist kein so weit hergeholtes Konzept. Tatsächlich bieten Websites wie Facebook Nutzern eine Möglichkeit, EXIF-Metadaten anzuzeigen, um technische Details über die von ihnen gehosteten Bilder zu erfahren, z. B. Farbtiefe, Kameramarke und -modell und andere Daten.

Die Logik zum Abrufen eines Bildes, zum Konvertieren in ein ArrayBuffer-Element und zum Extrahieren der EXIF-Metadaten kann jedoch teuer sein, wenn sie vollständig im Hauptthread ausgeführt wird. Glücklicherweise ermöglicht der Web-Worker-Bereich diese Arbeit außerhalb des Hauptthreads. Anschließend werden die EXIF-Metadaten mithilfe der Messaging-Pipeline des Web Workers als HTML-String an den Hauptthread zurückgeschickt und dem Nutzer angezeigt.

Wie sieht der Hauptthread ohne Web Worker aus?

Sehen wir uns zuerst an, wie der Hauptthread aussieht, wenn wir dies ohne einen Web Worker erledigen. Führen Sie dazu die folgenden Schritte aus:

  1. Öffnen Sie in Chrome einen neuen Tab und dann die zugehörigen Entwicklertools.
  2. Öffnen Sie den Bereich „Leistung“.
  3. Gehen Sie zu https://exif-worker.glitch.me/without-worker.html.
  4. Klicken Sie im Leistungssteuerfeld oben rechts im Bereich „Entwicklertools“ auf Datensatz.
  5. Fügen Sie diesen Bildlink oder einen anderen Link Ihrer Wahl in das Feld ein, der EXIF-Metadaten enthält. Klicken Sie dann auf die Schaltfläche Diese JPEG-Datei abrufen.
  6. Sobald die Schnittstelle die EXIF-Metadaten enthält, klicken Sie noch einmal auf Aufzeichnen, um die Aufzeichnung zu beenden.
Der Performance-Profiler, der die Aktivität der Anwendung zum Extrahieren von Bildmetadaten anzeigt, die vollständig im Hauptthread ausgeführt wird. Es gibt zwei umfangreiche Aufgaben: Bei der einen wird ein Abruf ausgeführt, um das angeforderte Bild abzurufen und zu decodieren, und bei der anderen werden die Metadaten aus dem Bild extrahiert.
Aktivität des Hauptthreads in der Anwendung zum Extrahieren von Bildmetadaten. Die gesamte Aktivität findet im Hauptthread statt.

Abgesehen von anderen möglicherweise vorhandenen Threads (z. B. Rasterthreads) erfolgt alles in der Anwendung im Hauptthread. Im Hauptthread geschieht Folgendes:

  1. Das Formular nimmt die Eingabe auf und sendet eine fetch-Anfrage, um den ersten Teil des Bildes abzurufen, der die EXIF-Metadaten enthält.
  2. Die Bilddaten werden in ein ArrayBuffer konvertiert.
  3. Das Skript exif-reader wird verwendet, um die EXIF-Metadaten aus dem Bild zu extrahieren.
  4. Aus den Metadaten wird ein HTML-String erstellt, der dann in den Metadaten-Viewer eingefügt wird.

Stellen wir uns dies mit einer Implementierung des gleichen Verhaltens gegenüber – jedoch mit einem Web-Worker.

Wie sieht der Hauptthread mit einem Web Worker aus?

Sie wissen nun, wie das Extrahieren der EXIF-Metadaten aus einer JPEG-Datei im Hauptthread aussieht. Sehen wir uns nun an, wie es aussieht, wenn ein Web-Worker in der Mischung ist:

  1. Öffnen Sie einen weiteren Tab in Chrome und die zugehörigen Entwicklertools.
  2. Öffnen Sie den Bereich „Leistung“.
  3. Gehen Sie zu https://exif-worker.glitch.me/with-worker.html.
  4. Klicken Sie im Leistungssteuerfeld oben rechts im Entwicklertools-Bereich auf die Schaltfläche zum Aufzeichnen.
  5. Fügen Sie diesen Bildlink in das Feld ein und klicken Sie auf die Schaltfläche Diese JPEG-Datei abrufen.
  6. Sobald die Oberfläche mit EXIF-Metadaten gefüllt ist, klicken Sie noch einmal auf die Schaltfläche zum Aufzeichnen, um die Aufzeichnung zu beenden.
Der Leistungsprofiler, der die Aktivität der Anwendung zum Extrahieren von Bildmetadaten anzeigt, die sowohl im Hauptthread als auch in einem Web Worker-Thread ausgeführt wird. Obwohl sich im Hauptthread immer noch lange Aufgaben befinden, sind diese wesentlich kürzer, da das Abrufen/Decodieren des Bildes und die Metadatenextraktion vollständig auf einem Web-Worker-Thread erfolgen. Bei der einzigen Arbeit des Hauptthreads werden Daten an den und vom Web Worker übergeben.
Aktivität des Hauptthreads in der Anwendung zum Extrahieren von Bildmetadaten. Beachten Sie, dass es einen zusätzlichen Web-Worker-Thread gibt, in dem die meiste Arbeit ausgeführt wird.

Dies ist die Leistungsfähigkeit eines Web Workers. Im Hauptthread wird nicht alles ausgeführt, außer das Einfügen von HTML in den Metadaten-Viewer erfolgt in einem separaten Thread. Das bedeutet, dass der Hauptthread für andere Aufgaben freigegeben wird.

Der vielleicht größte Vorteil besteht darin, dass im Gegensatz zur Version dieser Anwendung, die keinen Web Worker verwendet, das Skript exif-reader nicht in den Hauptthread geladen wird, sondern in den Web Worker-Thread. Das bedeutet, dass die Kosten für das Herunterladen, Parsen und Kompilieren des Skripts exif-reader außerhalb des Hauptthreads anfallen.

Beschäftigen wir uns nun mit dem Web-Worker-Code, der dies ermöglicht.

Ein Blick auf den Web Worker-Code

Es reicht nicht aus, den Unterschied zu sehen, den ein Web Worker macht. Es hilft auch, – zumindest in diesem Fall – zu verstehen, wie dieser Code aussieht, damit Sie wissen, was im Web Worker-Bereich möglich ist.

Beginnen Sie mit dem Hauptthread-Code, der ausgeführt werden muss, bevor der Web Worker das Bild eingeben kann:

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

Dieser Code wird im Hauptthread ausgeführt und richtet das Formular so ein, dass die Bild-URL an den Web Worker gesendet wird. Von dort startet der Web-Worker-Code mit einer importScripts-Anweisung, die das externe exif-reader-Skript lädt und dann die Nachrichtenpipeline für den Hauptthread einrichtet:

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

Dieses JavaScript richtet die Messaging-Pipeline so ein, dass wenn der Nutzer das Formular mit einer URL in einer JPEG-Datei sendet, die URL im Web Worker eingeht. Anschließend extrahiert der nächste Codeabschnitt die EXIF-Metadaten aus der JPEG-Datei, erstellt einen HTML-String und sendet diesen HTML-Code zurück an die window, um sie dem Nutzer anzuzeigen:

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

Es ist etwas zu lesen, aber es ist auch ein ziemlich komplizierter Anwendungsfall für Web Worker. Die Ergebnisse sind jedoch die Mühe wert und nicht nur auf diesen Anwendungsfall beschränkt. Sie können Web Worker für verschiedenste Zwecke verwenden, z. B. zum Isolieren von fetch-Aufrufen und Verarbeiten von Antworten oder zum Verarbeiten großer Datenmengen, ohne den Hauptthread zu blockieren – und das ist erst der Anfang.

Wenn Sie die Leistung Ihrer Webanwendungen verbessern möchten, sollten Sie sich überlegen, was in einem Web-Worker-Kontext vernünftigerweise möglich ist. Die Vorteile können beträchtlich sein und zu einer insgesamt besseren Nutzererfahrung für Ihre Website führen.