Web Workers in JavaScript
Run JavaScript in background threads with Web Workers — dedicated workers, shared workers, message passing, and offloading heavy computation.
What you'll learn
- ✓How Web Workers run JavaScript in background threads
- ✓Sending and receiving messages between main thread and workers
- ✓Transferable objects for zero-copy data transfer
- ✓Practical patterns: CPU-heavy tasks, data processing
Prerequisites
- •JavaScript basics (async, events)
- •Understanding of the single-threaded event loop
JavaScript is single-threaded. Heavy computation blocks the UI — animations freeze, clicks are ignored. Web Workers run JavaScript in a separate thread, keeping the main thread responsive.
Creating a worker
// main.js
const worker = new Worker("worker.js");
worker.postMessage({ type: "compute", data: [1, 2, 3, 4, 5] });
worker.onmessage = (event) => {
console.log("Result:", event.data);
};
worker.onerror = (error) => {
console.error("Worker error:", error.message);
};
// worker.js
self.onmessage = (event) => {
const { type, data } = event.data;
if (type === "compute") {
const result = data.reduce((sum, n) => sum + n, 0);
self.postMessage(result);
}
};
What workers CAN and CANNOT do
| Can do | Cannot do |
|---|---|
| CPU-heavy computation | Access the DOM |
| Fetch API, WebSocket | Use window object |
| IndexedDB | Access document |
setTimeout/setInterval | Use UI APIs (alert, confirm) |
| Import scripts | Share memory directly (without SharedArrayBuffer) |
Module workers
const worker = new Worker("worker.js", { type: "module" });
// worker.js — can use import
import { heavyComputation } from "./math.js";
self.onmessage = (event) => {
const result = heavyComputation(event.data);
self.postMessage(result);
};
Transferable objects
For large data, transfer ownership instead of copying.
// main.js
const buffer = new ArrayBuffer(1024 * 1024); // 1MB
worker.postMessage(buffer, [buffer]);
// buffer is now unusable in main thread — zero-copy transfer
Inline workers with Blob
No separate file needed:
const code = `
self.onmessage = (e) => {
const result = e.data.map(n => n * 2);
self.postMessage(result);
};
`;
const blob = new Blob([code], { type: "application/javascript" });
const worker = new Worker(URL.createObjectURL(blob));
worker.postMessage([1, 2, 3]);
worker.onmessage = (e) => console.log(e.data); // [2, 4, 6]
Promise-based wrapper
function runInWorker(workerFn, data) {
return new Promise((resolve, reject) => {
const code = `self.onmessage = (e) => {
const fn = ${workerFn.toString()};
const result = fn(e.data);
self.postMessage(result);
};`;
const blob = new Blob([code], { type: "application/javascript" });
const worker = new Worker(URL.createObjectURL(blob));
worker.onmessage = (e) => {
resolve(e.data);
worker.terminate();
};
worker.onerror = reject;
worker.postMessage(data);
});
}
const result = await runInWorker(
(numbers) => numbers.filter(n => isPrime(n)),
Array.from({ length: 100000 }, (_, i) => i)
);
Worker pool
Reuse workers instead of creating new ones for each task.
class WorkerPool {
constructor(workerScript, size) {
this.workers = Array.from({ length: size }, () => new Worker(workerScript));
this.queue = [];
this.available = [...this.workers];
}
run(data) {
return new Promise((resolve) => {
const task = { data, resolve };
const worker = this.available.pop();
if (worker) {
this.execute(worker, task);
} else {
this.queue.push(task);
}
});
}
execute(worker, task) {
worker.onmessage = (e) => {
task.resolve(e.data);
const next = this.queue.shift();
if (next) {
this.execute(worker, next);
} else {
this.available.push(worker);
}
};
worker.postMessage(task.data);
}
terminate() {
this.workers.forEach((w) => w.terminate());
}
}
Terminating workers
worker.terminate(); // immediate termination from main thread
self.close(); // graceful close from inside the worker
Summary
Web Workers move CPU-heavy JavaScript off the main thread. Use them for data processing, image manipulation, parsing, and any computation that would block the UI. Transfer large data with transferable objects for zero-copy performance. Use a worker pool to amortize creation cost across many tasks.
Related articles
- JavaScript JavaScript Web Workers: Run Code Off the Main Thread
Learn how to use JavaScript Web Workers to run CPU-intensive code off the main thread, keeping your UI responsive with practical examples and patterns.
- JavaScript SharedArrayBuffer and Atomics for Parallel JavaScript
Learn how to use SharedArrayBuffer and Atomics to share memory between threads, coordinate Web Workers, and build lock-free data structures in JavaScript.
- JavaScript Iterators and Generators in JavaScript
A deep dive into JavaScript iterators and generators: the iterator protocol, Symbol.iterator, generator functions, yield, async generators, and practical patterns for lazy evaluation and data pipelines.
- JavaScript JavaScript Closures Explained with Real Examples
A practical guide to JavaScript closures, lexical scope, the classic loop bug, and the patterns that make closures genuinely useful in production code.