Tools & Libraries: Kommunikation mit Web-Workern

Web Worker eignen sich hervorragend dazu, komplexe Berechnungen vom Haupt-Thread einer Webanwendung auszulagern und damit die Performance einer Anwendung zu verbessern. Eine interessante Bibliothek, die die Kommunikation zwischen Worker-Threads und Haupt-Thread vereinfacht, ist Comlink.

In Pocket speichern vorlesen Druckansicht
Lesezeit: 4 Min.
Von
  • Philip Ackermann
Inhaltsverzeichnis

Web Worker eignen sich hervorragend dazu, komplexe Berechnungen vom Haupt-Thread einer Webanwendung auszulagern und damit die Performance einer Anwendung zu verbessern. Eine interessante Bibliothek, die die Kommunikation zwischen Worker-Threads und Haupt-Thread vereinfacht, ist Comlink.

Die Kommunikation zwischen Worker-Threads und Haupt-Thread erfolgt bekanntermaßen nachrichtenbasiert mit Hilfe der Methode postMessage() (auf Seiten des Senders) und Event-Listenern für das message-Event (auf Seiten des Empfängers). Möchte man Funktionen innerhalb eines Workers aus dem Haupt-Thread heraus im RPC-Stil aufrufen, müssen eventuell noch Antwortnachrichten zu den entsprechenden Anfragenachrichten zugeordnet werden, in der Regel über das Mitschleifen einer Request-ID.

Die Bibliothek Comlink vereinfacht diese Kommunikation zwischen Haupt-Thread und Worker-Threads durch eine zusätzliche Abstraktionsschicht: Klassen, Objekte und Funktionen, die innerhalb eines Workers definiert werden, lassen sich mit Hilfe von Comlink direkt innerhalb des Haupt-Threads importieren und wie gewohnt verwenden. Beispielsweise lassen sich Objektinstanzen direkt über die importierte Klasse erzeugen und Objektmethoden direkt aufrufen, ohne dass Entwickler mit den Details der Worker-Kommunikation in Berührung kommen. Technisch realisiert Comlink dies durch Anwendung des Proxy-Patterns: für das jeweilige Objekt (bzw. die Klasse/die Funktion), das innerhalb des Worker-Codes definiert ist, erzeugt Comlink eine Proxy-Instanz im aufrufenden Code.

Dazu stellt Comlink drei Methoden zur Verfügung:

  • Comlink.expose() dient dazu, innerhalb eines Workers das Objekt zu definieren, welches nach außen hin bereitgestellt werden soll.
  • Comlink.proxy() erzeugt für eine Objektinstanz der Typen Worker, Window oder MessagePort ein Proxy-Objekt, welches die gesamte Nachrichtenkommunikation verbirgt und die API des zuvor im Worker über Comlink.expose() definierten Objekts als asynchrone API zur Verfügung stellt.
  • Comlink.proxyValue() funktioniert genau wie Comlink.proxy(), allerdings mit einem kleinen Unterschied: Während Comlink.proxy() eine Kopie des Originalobjekts erzeugt, arbeitet Comlink.proxyValue() wirklich als Proxy: ändert man das Proxy-Objekt im Haupt-Thread, ändert sich auch das ursprüngliche Objekt im Worker-Thread.

Comlink kann entweder direkt von der Projekt-Website heruntergeladen oder über npm installiert werden:

npm install comlinkjs

Innerhalb des Codes für den Worker definiert man anschließend beliebige Objekte (Klassen, Funktionen, etc.) wie im folgenden Listing die Klasse Calculator (die in der Praxis natürlich etwas Speicherintensiveres als bloß die Summe zweier Zahlen berechnen würde) und übergibt sie der Methode Comlink.expose(). Dies ist notwendig, damit Comlink bei der Definition mehrerer Objekte intern weiß, welches dasjenige ist, für das später das Proxy-Objekt erstellt werden soll.

importScripts("/node_modules/comlinkjs/comlink.global.min.js");
class Calculator {
sum(x = 0, y = 0) {
return x + y;
}
}
Comlink.expose(Calculator, self);

Auf Seiten des Haupt-Threads erstellt man anschließend wie gewohnt eine Instanz von Worker, übergibt diese dann aber der Methode Comlink.proxy(). Im folgenden Beispiel wird auf diese Weise ein Proxy für die im Worker definierten Klasse Calculator erzeugt. Anschließend lassen sich über await new Calculator() neue Objektinstanzen erstellen. Das await ist dabei notwendig, da die Kommunikation zwischen Haupt-Thread und Worker-Thread asynchron abläuft. Dies gilt auch für andere Aufrufe der Proxy-API wie im Beispiel der Aufruf von sum().

<!doctype html>
<script src="/node_modules/comlinkjs/comlink.global.min.js"></script>
<script>
async function init() {
const worker = new Worker('worker.js');
const Calculator = Comlink.proxy(worker);
const calculator = await new Calculator();
const result = await calculator.sum(80, 90);
console.log(result);
};
init();
</script>

Comlink versucht die technischen Details der Nachrichtenkommunikation mit Web-Workern zu vereinfachen und ist ein interessantes Beispiel für die Anwendung des Proxy-Patterns. Comlink kann aber nicht nur bei der Kommunikation mit Web-Workern verwendet werden, sondern auch bei der Kommunikation mit anderen Browserfenstern, Frames und generell allem, was das MessagePort-Interface der Channel Messaging API implementiert. ()