Zruck zum Blog, du Zniachtl
Sep. 2026·3 Min. zum LesnVo da KI gmocht

Web Workers für rechenintensive Aufgaben vastehn

#javascript#performance#browser

JavaScript lauft auf oam einzigen Thread, des heißt, a rechenintensive Aufgabe - an riesigen JSON-Brocken parsn, a Bild skaliern, an Pfadfindungs-Algorithmus laufn lassn - blockiert ois ondere auf da Seitn, solang's lauft: koa Scrollen, koa Klicks, koa Animationen. Web Workers lösn des, indem's dir an echten Hintergrund-Thread gebn, und sie wern seit mehr ois zehn Joar in jedem relevanten Browser unterstützt.

Warum da Main Thread erstickt

Da Main Thread vom Browser mocht Doppelschicht: Er führt dein JavaScript aus und kümmert si ums Rendering, Layout und Input-Events. Wenn a for-Schleifn 800ms braucht, is de Seitn de ganzn 800ms eingfrorn - ohne Ausnahm, ohne "mach's halt async". async/await und Promises hüfn do a ned; sie plonen, wann Code im Vergleich zu ondern JavaScript lauft, aber sie schiebn koa Orbeit vom Thread weg. A synchrone Schleifn bleibt synchron, egal wie vül awaits drumherum stengan.

// This blocks the main thread completely, Promise or not
async function findPrimes(limit) {
  const primes = [];
  for (let n = 2; n <= limit; n++) {
    if (isPrime(n)) primes.push(n);
  }
  return primes;
}

Orbeit in an Worker auslagern

A Web Worker lauft in seim eigenen globalen Kontext, ohne Zugriff aufs DOM, window oda auf d' Variablen von deiner Seitn. Kommunikation geht ausschließlich über Message Passing.

// worker.js
self.onmessage = (event) => {
  const { limit } = event.data;
  const primes = [];
  for (let n = 2; n <= limit; n++) {
    if (isPrime(n)) primes.push(n);
  }
  self.postMessage({ primes });
};

function isPrime(n) {
  if (n < 2) return false;
  for (let i = 2; i * i <= n; i++) {
    if (n % i === 0) return false;
  }
  return true;
}
// main.js
const worker = new Worker('worker.js');

worker.postMessage({ limit: 1_000_000 });

worker.onmessage = (event) => {
  console.log(`Found ${event.data.primes.length} primes`);
};

De Seitn bleibt voll reaktionsfähig, während worker.js a Million Zohln durchackert, weil de Schleifn jetzt auf ana ganz eigenen OS-Thread lauft.

An Worker in a Promise einpacken

Des rohe onmessage/postMessage wird unhandlich, sobois Anfragen mit Antworten zuordnen musst. A klaner Wrapper mocht Worker wia jede ondere Async-API:

function runInWorker(worker, payload) {
  return new Promise((resolve, reject) => {
    const handleMessage = (event) => {
      cleanup();
      resolve(event.data);
    };
    const handleError = (error) => {
      cleanup();
      reject(error);
    };
    const cleanup = () => {
      worker.removeEventListener('message', handleMessage);
      worker.removeEventListener('error', handleError);
    };
    worker.addEventListener('message', handleMessage);
    worker.addEventListener('error', handleError);
    worker.postMessage(payload);
  });
}
const result = await runInWorker(worker, { limit: 1_000_000 });
console.log(result.primes.length);

De Version behandelt nur oane laufende Anfrage auf amoi - für vüle Foi reicht des, aber wennst mehrere gleichzeitige Anfragen an den söbn Worker schicken muasst, versiech jede Nachricht mit ana ID und ordne Antworten per ID zua, statt di auf oa einziges offenes Promise zum valossn.

Wos tatsächlich kopiert und wos übertrogn wird

Standardmäßig klont postMessage de gsendeten Daten mit dem Structured-Clone-Algorithmus - Objekte, Arrays, Maps, Dates und Typed Arrays überstehn de Reise, aber Funktionen, DOM-Knotn und Klasseninstanzen mit Methoden ned. Bei großen Binärdaten (a riesiger ArrayBuffer aus Bild- oda Audiobearbeitung) is Klonen Verschwendung - du kopierst Megabytes üba Threadgrenzen.

Transferable Objects vermeiden des Kopieren komplett, indem's den Besitz an de ondere Seitn übergebn:

const buffer = new ArrayBuffer(64 * 1024 * 1024); // 64MB
worker.postMessage({ buffer }, [buffer]);
// `buffer` is now unusable in this thread — ownership moved to the worker

Nach ana Übertragung is buffer.byteLength auf da Sendeseitn 0. Des is a echte Besitzübergabe, koa geteilte Referenz - nur oane Seitn kann's gleichzeitig nutzn, und genau deswegn is es schnö: es braucht koa Kopie.

Wann a Worker des falsche Werkzeug is

Worker hom echten Overhead: Oan hochzufahrn kost a poar Millisekunden, und jede Nachricht über de Grenze geht durch Structured Cloning (außa bei Transfer). Bei Orbeit, de in a poar Millisekunden fertig is, kann der Overhead de Ersparnis übersteigen. Worker lohnen si für:

  • Parsn oda Transformiern großer Datensätze (CSV, JSON, Binärformate)
  • Bild-/Video-/Audiobearbeitung (Skaliern, Filter, Encoding)
  • Komplexe, CPU-gebundene Algorithmen (Pfadfindung, Physiksimulation, Kryptografie)
  • Olles, wos sonst sichtbares Ruckeln bei da Interaktion verursachen würd

Falsch sand sie für olles DOM-Bezogene (sie kinnan's ned anrührn), für olles mit niedriger Latenz bei winzigen Eingaben, oda für Orbeit, de eh scho I/O-gebunden is (a fetch-Aufruf blockiert den Main Thread eh ned - er is scho asynchron).

Fazit

Da Main Thread is a geteilte Ressource zwischen deim Code und da Fähigkeit vom Browser, zum Rendern und auf Eingaben zum reagiern - a lange synchrone Aufgabe lasst beide valhungern. Web Workers gebn rechenintensiver Orbeit an echten eigenen Thread statt nur an anderen Platz in da Event-Loop-Queue, und des is des oane, wos async/await alloa nie kann.

NeicherWia da Virtual DOM wirklich funktioniertElterDebouncing und Throttling: Hochfrequente Events in JavaScript zähmen