Konkreter Anwendungsfall für Web Worker

Im letzten Modul haben Sie einen Überblick über Web-Worker erhalten. Web-Worker können die Reaktionsfähigkeit bei der Eingabe verbessern, indem JavaScript vom Hauptthread in separate Web-Worker-Threads verschoben wird. Dies kann dazu beitragen, die Interaction to Next Paint (INP) Ihrer Website zu verbessern, wenn Sie Aufgaben 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 Webworker vorgestellt.

Ein solcher Anwendungsfall könnte eine Website sein, die Exif-Metadaten aus einem Bild entfernen muss. Das ist kein so abwegiges Konzept. Auf Websites wie Flickr können Nutzer Exif-Metadaten ansehen, um technische Details zu den gehosteten Bildern zu erfahren, z. B. Farbtiefe, Kameramarke und -modell sowie andere Daten.

Die Logik zum Abrufen eines Bildes, zum Konvertieren in ein ArrayBuffer und zum Extrahieren der EXIF-Metadaten kann jedoch potenziell aufwendig sein, wenn sie vollständig im Hauptthread ausgeführt wird. Glücklicherweise kann diese Arbeit im Web Worker-Bereich außerhalb des Haupt-Threads ausgeführt werden. Über die Messaging-Pipeline des Web-Workers werden die Exif-Metadaten dann als HTML-String zurück an den Hauptthread übertragen und dem Nutzer angezeigt.

So sieht der Haupt-Thread ohne Web Worker aus

Sehen wir uns zuerst an, wie der Hauptthread aussieht, wenn wir diese Arbeit ohne Web-Worker ausführen. Führen Sie dazu die folgenden Schritte aus:

  1. Öffnen Sie einen neuen Tab in Chrome und dann die Entwicklertools.
  2. Öffnen Sie den Leistungsbereich.
  3. Rufen Sie https://chrome.dev/learn-performance-exif-worker/without-worker.html auf.
  4. Klicken Sie im Bereich „Leistung“ rechts oben im Bereich für die Entwicklertools auf Aufzeichnen.
  5. Fügen Sie diesen Bildlink oder einen anderen Link Ihrer Wahl, der Exif-Metadaten enthält, in das Feld ein und klicken Sie auf die Schaltfläche Get that JPEG! (JPEG abrufen).
  6. Sobald die Exif-Metadaten in der Benutzeroberfläche angezeigt werden, klicken Sie noch einmal auf Aufzeichnen, um die Aufzeichnung zu beenden.
Der Leistungs-Profiler zeigt die Aktivität der App zum Extrahieren von Bildmetadaten, die vollständig im Hauptthread stattfindet. Es gibt zwei umfangreiche Aufgaben, die lange dauern: Eine ruft das angeforderte Bild ab und decodiert es, die andere extrahiert die Metadaten aus dem Bild.
Aktivität im Hauptthread in der App zum Extrahieren von Bildmetadaten. Beachten Sie, dass die gesamte Aktivität im Hauptthread erfolgt.

Abgesehen von anderen Threads, die möglicherweise vorhanden sind, z. B. Rasterizer-Threads usw., findet alles in der App im Hauptthread statt. Im Haupt-Thread passiert Folgendes:

  1. Das Formular nimmt die Eingabe entgegen und sendet eine fetch-Anfrage, um den ersten Teil des Bildes mit den EXIF-Metadaten abzurufen.
  2. Die Bilddaten werden in eine ArrayBuffer konvertiert.
  3. Das exif-reader-Skript wird verwendet, um die EXIF-Metadaten aus dem Bild zu extrahieren.
  4. Die Metadaten werden extrahiert, um einen HTML-String zu erstellen, der dann in der Metadatenansicht angezeigt wird.

Sehen wir uns nun eine Implementierung desselben Verhaltens an, aber mit einem Webworker.

So sieht der Hauptthread mit einem Web-Worker aus

Nachdem Sie nun gesehen haben, wie die Exif-Metadaten aus einer JPEG-Datei im Hauptthread extrahiert werden, sehen Sie sich an, wie es aussieht, wenn ein Web-Worker beteiligt ist:

  1. Öffnen Sie einen weiteren Tab in Chrome und dann die Entwicklertools.
  2. Öffnen Sie den Leistungsbereich.
  3. Rufen Sie https://chrome.dev/learn-performance-exif-worker/with-worker.html auf.
  4. Klicken Sie im Leistungsbereich rechts oben im Bereich für die Entwicklertools auf die Aufnahmeschaltfläche.
  5. Fügen Sie diesen Bildlink in das Feld ein und klicken Sie auf die Schaltfläche Get that JPEG! (JPEG abrufen).
  6. Sobald die Exif-Metadaten in der Benutzeroberfläche angezeigt werden, klicken Sie noch einmal auf die Aufnahmeschaltfläche, um die Aufnahme zu beenden.
Der Leistungsprofiler zeigt die Aktivität der App zum Extrahieren von Bildmetadaten im Hauptthread und in einem Webworker-Thread. Es gibt zwar immer noch lange Aufgaben im Hauptthread, aber sie sind deutlich kürzer. Das Abrufen/Decodieren von Bildern und das Extrahieren von Metadaten erfolgen vollständig in einem Web-Worker-Thread. Die einzige Aufgabe des Haupt-Threads besteht darin, Daten an den Web-Worker zu übergeben und von ihm zu empfangen.
Aktivität im Hauptthread der App zum Extrahieren von Bildmetadaten. Beachten Sie, dass es einen zusätzlichen Web-Worker-Thread gibt, in dem der Großteil der Arbeit erledigt wird.

Das ist die Stärke eines Web-Workers. Anstatt alles im Hauptthread zu erledigen, wird alles außer dem Einfügen von HTML in die Metadatenansicht in einem separaten Thread ausgeführt. Das bedeutet, dass der Hauptthread für andere Aufgaben frei ist.

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

Sehen wir uns nun den Web-Worker-Code an, der das alles ermöglicht.

Web-Worker-Code

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

Beginnen Sie mit dem Code für den Hauptthread, der ausgeführt werden muss, bevor der Web-Worker zum Einsatz kommen 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. Der Webworker-Code beginnt mit einer importScripts-Anweisung, mit der das externe exif-reader-Skript geladen wird. Anschließend wird die Messaging-Pipeline zum Hauptthread eingerichtet:

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

Mit diesem JavaScript-Code wird die Messaging-Pipeline eingerichtet. Wenn der Nutzer das Formular mit einer URL zu einer JPEG-Datei einreicht, wird die URL an den Web-Worker gesendet. Der folgende Code extrahiert die Exif-Metadaten aus der JPEG-Datei, erstellt einen HTML-String und sendet diesen HTML-String zurück an window, damit er dem Nutzer angezeigt werden kann:

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

Es ist einiges zu lesen, aber das ist auch ein ziemlich komplexer Anwendungsfall für Webworker. Die Ergebnisse sind jedoch die Mühe wert und nicht nur auf diesen Anwendungsfall beschränkt. Webworker können für viele Dinge verwendet werden, z. B. zum Isolieren von fetch-Aufrufen und zum Verarbeiten von Antworten oder zum Verarbeiten großer Datenmengen, ohne den Hauptthread zu blockieren.

Wenn Sie die Leistung Ihrer Webanwendungen verbessern möchten, sollten Sie sich zuerst überlegen, was in einem Web-Worker-Kontext sinnvoll ist. Die Vorteile können erheblich sein und zu einer insgesamt besseren Nutzererfahrung auf Ihrer Website führen.