Threading im Web mit Modul-Workern

Das Verschieben schwerer Arbeiten in Hintergrund-Threads ist jetzt mit JavaScript-Modulen in Web Workern einfacher.

JavaScript ist Single-Threaded, das heißt, es kann jeweils nur eine Operation ausführen. Dies ist intuitiv und funktioniert im Web in vielen Fällen gut, kann jedoch problematisch werden, schwere Aufgaben wie Datenverarbeitung, Parsing, Berechnung oder Analyse zu erledigen. Da immer mehr Menschen über das Web bereitgestellt werden, besteht ein erhöhter Bedarf Datenverarbeitung.

Auf der Webplattform ist das Hauptprimitiv für Threading und Parallelität das Web- Workers API hinzu. Worker sind eine einfache Abstraktion, die auf dem Betriebssystem basiert. Threads, die eine API zur Nachrichtenweitergabe verfügbar machen für die Kommunikation zwischen Threads. Dies kann bei kostspieligen Berechnungen mit großen Datasets arbeiten, sodass der Hauptthread während der Ausführung auf einem oder mehreren Hintergrundthreads verwendet werden.

Hier ist ein typisches Beispiel für die Worker-Nutzung, bei dem ein Worker-Skript Nachrichten vom Haupt- Thread und antwortet, indem er eigene Nachrichten zurücksendet:

page.js:

const worker = new Worker('worker.js');
worker.addEventListener('message', e => {
  console.log(e.data);
});
worker.postMessage('hello');

worker.js:

addEventListener('message', e => {
  if (e.data === 'hello') {
    postMessage('world');
  }
});

Die Web Worker API ist seit über zehn Jahren in den meisten Browsern verfügbar. Dabei dass die Mitarbeiter hervorragenden Browser-Support haben und gut optimiert sind, bedeutet dies auch, dass sie lange älteren JavaScript-Modulen. Da es beim Entwerfen der Worker kein Modulsystem gab, zum Laden von Code in einen Worker und zum Erstellen von Skripts ist dem synchronen Skript ähnlich. die 2009 gebräuchlich sind.

Verlauf: klassische Worker

Der Worker-Konstruktor verwendet einen klassischen Skript-URL. relativ zur Dokument-URL. Sie gibt sofort einen Verweis auf die neue Worker-Instanz zurück, enthält eine Messaging-Oberfläche sowie eine terminate()-Methode, die sofort beendet und zerstört den Worker.

const worker = new Worker('worker.js');

Zum Laden von zusätzlichem Code steht in den Web Workern eine importScripts()-Funktion zur Verfügung, sie unterbricht die Ausführung des Workers, um jedes Skript abzurufen und auszuwerten. Außerdem werden Skripts ausgeführt, wie bei einem klassischen <script>-Tag, d. h. die Variablen in einem Skript können von Variablen in einer anderen überschrieben werden.

worker.js:

importScripts('greet.js');
// ^ could block for seconds
addEventListener('message', e => {
  postMessage(sayHello());
});

greet.js:

// global to the whole worker
function sayHello() {
  return 'world';
}

Aus diesem Grund haben Web Worker in der Vergangenheit einen enormen Effekt auf die Architektur eines . Entwickler mussten clevere Tools und Behelfslösungen entwickeln, um Web Worker verwenden, ohne auf moderne Entwicklungsmethoden zu verzichten. Zum Beispiel verwenden Bundler Webpack bettet eine kleine Implementierung des Modulladeprogramms in generierten Code ein, der importScripts() verwendet für das Laden von Code, fasst Module in Funktionen ein, um Variablenkollisionen zu vermeiden und Abhängigkeitsimporte und -exporte.

Modul-Worker eingeben

Ein neuer Modus für Web-Worker mit den Ergonomie- und Leistungsvorteilen von JavaScript Module, die als Modul-Worker bezeichnet werden, in Chrome 80. Die Der Worker-Konstruktor akzeptiert jetzt eine neue {type:"module"}-Option, die das Laden des Skripts und Ausführung entsprechend <script type="module">.

const worker = new Worker('worker.js', {
  type: 'module'
});

Da es sich bei Modul-Workern um Standard-JavaScript-Module handelt, können sie Import- und Exportanweisungen verwenden. Als Abhängigkeiten bei allen JavaScript-Modulen werden nur einmal in einem bestimmten Kontext (Hauptthread, Worker usw.) und alle zukünftigen Importe verweisen auf die bereits ausgeführte Modulinstanz. Das Laden und die Ausführung von JavaScript-Modulen wird auch durch Browser optimiert. Die Abhängigkeiten eines Moduls vor der Ausführung des Moduls geladen, sodass ganze Modulbäume parallel. Beim Laden des Moduls wird auch geparster Code im Cache gespeichert. und in einem Worker nur einmal geparst werden müssen.

Durch den Wechsel zu JavaScript-Modulen können auch dynamische import für Lazy Loading von Code, ohne die Ausführung von für den Worker. Der dynamische Import ist wesentlich expliziter als das Laden von Abhängigkeiten mit importScripts(). da die Exporte des importierten Moduls zurückgegeben werden, anstatt sich auf globale Variablen zu verlassen.

worker.js:

import { sayHello } from './greet.js';
addEventListener('message', e => {
  postMessage(sayHello());
});

greet.js:

import greetings from './data.js';
export function sayHello() {
  return greetings.hello;
}

Um eine hervorragende Leistung zu gewährleisten, ist die alte importScripts()-Methode innerhalb des Moduls nicht verfügbar. Arbeiter. Beim Wechsel von Workern zur Verwendung von JavaScript-Modulen wird der gesamte Code streng Modus an. Anderes Die wichtigste Änderung besteht darin, dass der Wert von this im übergeordneten Bereich eines JavaScript-Moduls undefined, während bei klassischen Workern der Wert dem globalen Geltungsbereich des Workers entspricht. Zum Glück gibt es war schon immer ein globaler self, der auf den globalen Geltungsbereich verweist. Es ist verfügbar in alle Worker-Typen, einschließlich Service Workern, sowie im DOM.

Worker mit modulepreload vorab laden

Eine wesentliche Leistungsverbesserung für Modul-Worker ist die Möglichkeit, Workern und ihren Abhängigkeiten. Mit Modul-Workern werden Skripts standardmäßig geladen und ausgeführt. JavaScript-Module, d. h., sie können vorab geladen und mit modulepreload sogar vorab geparst werden:

<!-- preloads worker.js and its dependencies: -->
<link rel="modulepreload" href="worker.js">

<script>
  addEventListener('load', () => {
    // our worker code is likely already parsed and ready to execute!
    const worker = new Worker('worker.js', { type: 'module' });
  });
</script>

Vorab geladene Module können auch von den Hauptthread- und Modul-Workern verwendet werden. Dies ist nützlich für Module, die in beiden Kontexten importiert werden oder in Fällen, in denen dies nicht im Voraus möglich ist ob ein Modul im Hauptthread oder in einem Worker verwendet wird.

Bisher waren die Optionen zum Vorabladen von Web Worker-Skripts nicht unbedingt zuverlässig sein. Klassische Worker hatten einen eigenen „Worker“ Ressourcentyp für das Vorabladen, aber kein Browser haben <link rel="preload" as="worker"> implementiert. Daher ist die primäre Technik zum Vorabladen von Web Workern verfügbar war, war die Verwendung von <link rel="prefetch">, im HTTP-Cache. In Kombination mit den richtigen Caching-Headern Damit vermeiden Sie, dass die Worker-Instanziierung mit dem Herunterladen des Worker-Skripts warten muss. Im Gegensatz zu modulepreload: Diese Technik hat das Vorabladen von Abhängigkeiten oder die Vorbereitung nicht unterstützt.

Was ist mit gemeinsamen Workern?

Freigegebene Worker haben unterstützt seit Chrome 83 JavaScript-Module. Wie engagierte Mitarbeitende Beim Erstellen eines freigegebenen Workers mit der Option {type:"module"} wird das Worker-Skript jetzt als statt in einem klassischen Skript:

const worker = new SharedWorker('/worker.js', {
  type: 'module'
});

Vor der Unterstützung von JavaScript-Modulen erwartete der SharedWorker()-Konstruktor nur einen URL und ein optionales name-Argument. Dies funktioniert auch weiterhin bei der klassischen Verwendung von gemeinsam genutzten Workern. jedoch Zum Erstellen von freigegebenen Workern für Modul muss das neue Argument options verwendet werden. Die verfügbaren Optionen sind dieselben wie für einen dedizierten Worker, einschließlich der Option name, das vorherige name-Argument.

Wie sieht es mit dem Service Worker aus?

Die Service Worker-Spezifikation wurde bereits JavaScript-Modul als Einstiegspunkt unter Verwendung derselben {type:"module"}-Option wie Modul-Worker Diese Änderung muss jedoch in Browsern noch nicht implementiert werden. Sobald dies geschehen ist, um einen Service Worker mithilfe eines JavaScript-Moduls und folgendem Code zu instanziieren:

navigator.serviceWorker.register('/sw.js', {
  type: 'module'
});

Nachdem die Spezifikation aktualisiert wurde, beginnen die Browser damit, das neue Verhalten zu implementieren. Dies nimmt einige Zeit in Anspruch, da das Aufrufen von JavaScript mit einigen zusätzlichen Komplikationen Module für den Service Worker. Die Service Worker-Registrierung muss importierte Skripts vergleichen. mit ihren vorherigen im Cache gespeicherten Versionen, bestimmt, ob ein Update ausgelöst werden soll. Dies muss für JavaScript-Module implementiert werden. wenn sie für Service Worker verwendet werden. Außerdem müssen Service Worker in der Lage sein, für Skripts verwenden. nach Updates suchen.

Zusätzliche Ressourcen und weiterführende Informationen