MWCodebymw.de ↗
JavaScript

Web Worker – rechnen, ohne die Oberfläche einzufrieren

JavaScript hat im Browser genau einen Faden für alles: Klicks, Animationen, Layout – und deine Rechenschleife. Dauert die zwei Sekunden, ist die Seite zwei Sekunden lang tot. Ein Worker nimmt die Arbeit auf einen eigenen Faden.

Eine CSV mit 200.000 Zeilen parsen, ein Bild pixelweise umrechnen, eine große Liste sortieren und gruppieren: Solche Aufgaben blockieren den Hauptfaden. Erkennbar ist das sofort – der Klick reagiert nicht, die Animation ruckelt, der Tab meldet „reagiert nicht". setTimeout hilft nicht, es verschiebt das Problem nur.

Ein Web Worker läuft in einem eigenen Faden, mit eigenem Speicher, und redet über Nachrichten mit der Seite.

Der Worker

// rechner.worker.js
self.addEventListener('message', (e) => {
  const { zeilen } = e.data;

  let summe = 0;
  for (const z of zeilen) summe += z.betrag;        // die teure Schleife

  self.postMessage({ summe, anzahl: zeilen.length });
});

Die Seite

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

worker.addEventListener('message', (e) => {
  anzeige.textContent = `${e.data.anzahl} Posten, Summe ${e.data.summe.toFixed(2)} €`;
});
worker.addEventListener('error', (e) => {
  console.error('Worker gescheitert:', e.message);
});

worker.postMessage({ zeilen });      // die Seite bleibt bedienbar

Während der Worker rechnet, läuft die Oberfläche weiter: Spinner drehen sich, Knöpfe reagieren, Scrollen ruckelt nicht.

Einmal fragen, einmal antworten – als Promise

Roh ist die Nachrichten-API unhandlich. Ein kleiner Wrapper macht daraus einen normalen await-Aufruf:

function frage(worker, daten, transfer = []) {
  return new Promise((ok, fehler) => {
    const kanal = new MessageChannel();
    kanal.port1.onmessage = (e) => ok(e.data);
    kanal.port1.onmessageerror = () => fehler(new Error('Antwort unlesbar'));
    worker.postMessage(daten, [kanal.port2, ...transfer]);
  });
}

const { summe } = await frage(worker, { zeilen });

Große Daten nicht kopieren, sondern übergeben

Nachrichten werden standardmäßig kopiert (strukturiertes Klonen). Bei einem 50-MB-Puffer ist das der eigentliche Kostenfaktor. Übertragbare Objekte wandern stattdessen den Besitzer:

const puffer = new ArrayBuffer(50 * 1024 * 1024);
worker.postMessage({ puffer }, [puffer]);   // danach ist `puffer` hier leer (detached)

Fallstrick

Im Worker gibt es kein DOM: kein document, kein window, keine Elemente. Er rechnet und schickt Ergebnisse; das Zeichnen bleibt Sache der Seite (Ausnahme: OffscreenCanvas). Weiter: Ein Worker kostet Speicher und Startzeit – für eine Aufgabe von 5 ms lohnt er nicht, für alles ab etwa 50 ms schon. Und beende ihn, wenn er nicht mehr gebraucht wird (worker.terminate()), sonst bleibt er die ganze Sitzung am Leben. Beim Laden per new Worker(...) gilt außerdem die Same-Origin-Regel – die Datei muss von deiner eigenen Domain kommen.

Wenn deine Anwendung bei großen Datenmengen hakt, schaue ich mir das gern an: bymw.de.

Quellen

#JavaScript#Web Worker#Performance#Nebenläufigkeit#Browser

Du brauchst mehr als ein Snippet?

Ich entwickle Android-Apps in Kotlin und moderne Websites für Selbstständige und kleine Unternehmen — von der ersten Idee bis zum Release.

Projekt anfragen →