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 bedienbarWä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
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 →Verwandte Snippets
Formulare ohne Neuladen abschicken – FormData und fetch
Ein Formular per JavaScript abzuschicken heißt nicht, jedes Feld einzeln auszulesen. FormData nimmt das ganze Formular, fetch schickt es weg – inklusive Dateien, und mit einer Fehlermeldung, die der Besucher versteht.
CustomEvent – Bausteine, die sich gegenseitig Bescheid sagen, ohne sich zu kennen
Sobald zwei Teile einer Seite aufeinander reagieren sollen, landet man schnell bei globalen Variablen oder Funktionsaufrufen quer durchs Projekt. Eigene Events entkoppeln das – mit denselben Mitteln, die der Browser selbst benutzt.
Promise.allSettled – wenn ein einziger Fehlschlag nicht alles kippen darf
Promise.all bricht beim ersten Fehler ab und wirft alle anderen Ergebnisse weg. Für ein Dashboard aus fünf Quellen ist das genau falsch: Vier Kacheln könnten Daten zeigen, aber die Seite bleibt leer.