JavaScript is single-threaded, but Web Workers let you run code in background threads. At Google, we offload heavy computation to workers.
Dedicated Workers
// main.js
const worker = new Worker("worker.js");
worker.postMessage({ type: "sort", data: hugeArray });
worker.onmessage = (e) => {
console.log("Sorted:", e.data);
};
// worker.js
self.onmessage = (e) => {
if (e.data.type === "sort") {
const sorted = e.data.data.sort((a, b) => a - b);
self.postMessage(sorted);
}
};Shared Workers
Shared across multiple tabs/windows of the same origin. Perfect for shared state like WebSocket connections.
const shared = new SharedWorker("shared.js");
shared.port.postMessage("hello");
shared.port.onmessage = (e) => console.log(e.data);Transferable Objects
By default, postMessage copies data (structured clone). For large ArrayBuffers, use transfer to move ownership — zero-copy, instant.
const buffer = new ArrayBuffer(1024 * 1024 * 100); // 100MB
// Transfer (not copy) — instant, but buffer becomes unusable in main thread
worker.postMessage(buffer, [buffer]);Comlink: Simplified Worker API
// worker.js
import * as Comlink from "comlink";
const api = {
async processImage(imageData) {
// Heavy computation
return processedResult;
}
};
Comlink.expose(api);
// main.js
import * as Comlink from "comlink";
const worker = new Worker("worker.js");
const api = Comlink.wrap(worker);
const result = await api.processImage(data); // Looks like a normal async call!What Workers CAN'T Do
- Access the DOM
- Use window, document, or parent
- Access synchronous XHR (don't anyway)
Use Cases
- Image/video processing
- Large dataset sorting/filtering
- Encryption/hashing
- Syntax highlighting (Monaco editor uses workers)
- WebAssembly computation