JavaScript is single-threaded, which means heavy computation blocks the UI — no smooth animations, no responsive inputs, no updates while processing. Web Workers run JavaScript in a background thread, keeping the main thread free. Firefox has full Web Worker support including SharedArrayBuffer and Atomics.
Creating a Web Worker
Workers are created from a JavaScript file:
// main.js
const worker = new Worker('/workers/math.js');
// Send message to worker
worker.postMessage({ type: 'compute', n: 1000000 });
// Receive message from worker
worker.onmessage = (event) => {
console.log('Result:', event.data.result);
};
worker.onerror = (error) => {
console.error('Worker error:', error.message);
};
// workers/math.js
self.onmessage = (event) => {
if (event.data.type === 'compute') {
const result = heavyComputation(event.data.n);
self.postMessage({ result });
}
};
function heavyComputation(n) {
let sum = 0;
for (let i = 0; i < n; i++) {
sum += Math.sqrt(i) * Math.sin(i);
}
return sum;
}
postMessage serializes data using the Structured Clone algorithm — more powerful than JSON (supports TypedArrays, Maps, Sets, RegExp, etc.).
Transferable Objects (Zero-Copy)
For large data (ArrayBuffers, ImageBitmaps), use transferables to avoid copying:
// Without transfer: copies the buffer (slow for large data)
const buffer = new ArrayBuffer(1000000);
worker.postMessage({ buffer });
// With transfer: moves ownership to worker (zero copy)
worker.postMessage({ buffer }, [buffer]);
// After transfer, 'buffer' is detached in main thread — can't use it anymore
The worker receives the buffer and can transfer it back:
// In worker
self.onmessage = (event) => {
const { buffer } = event.data;
const view = new Float32Array(buffer);
// Process...
self.postMessage({ buffer }, [buffer]); // Transfer back
};
Use Cases
Image Processing
// Expensive: blur, resize, filter operations
const imageWorker = new Worker('/workers/image-processor.js');
function processImage(imageData) {
return new Promise((resolve) => {
imageWorker.postMessage(
{ imageData: imageData.data.buffer, width: imageData.width, height: imageData.height },
[imageData.data.buffer] // Transfer the pixel data
);
imageWorker.onmessage = (e) => resolve(new ImageData(e.data.result, e.data.width));
});
}
Large Data Parsing
// Parse a large CSV/JSON file without freezing the UI
const parseWorker = new Worker('/workers/parser.js');
async function parseLargeFile(fileContent) {
return new Promise((resolve, reject) => {
parseWorker.postMessage({ content: fileContent });
parseWorker.onmessage = (e) => resolve(e.data.records);
parseWorker.onerror = (e) => reject(e);
});
}
// In worker:
// self.onmessage = (e) => {
// const records = parseCSV(e.data.content);
// self.postMessage({ records });
// }
Cryptographic Operations
// Hash passwords, generate keys, encrypt data
const cryptoWorker = new Worker('/workers/crypto.js');
async function hashPassword(password) {
return new Promise((resolve) => {
cryptoWorker.postMessage({ type: 'hash', password });
cryptoWorker.onmessage = (e) => resolve(e.data.hash);
});
}
Worker Module Syntax
Modern workers support ES modules:
// main.js
const worker = new Worker('/workers/modern.js', { type: 'module' });
// workers/modern.js (ES module worker)
import { heavyCalculation } from '../utils/math.js';
self.onmessage = async (event) => {
const result = await heavyCalculation(event.data);
self.postMessage({ result });
};
Shared Workers
Share a worker between multiple tabs/windows:
// In each tab/window:
const shared = new SharedWorker('/workers/shared.js');
shared.port.start();
shared.port.postMessage({ type: 'subscribe', channel: 'updates' });
shared.port.onmessage = (event) => {
console.log('Update from shared worker:', event.data);
};
// workers/shared.js
const ports = new Set();
self.onconnect = (event) => {
const port = event.ports[0];
ports.add(port);
port.start();
port.onmessage = (e) => {
// Broadcast to all connected tabs
ports.forEach(p => p.postMessage({ broadcast: e.data }));
};
};
Shared Workers are useful for: shared state across tabs, deduplicating WebSocket connections, shared cache management.
OffscreenCanvas: Rendering in a Worker
Move canvas rendering to a worker:
// main.js
const canvas = document.getElementById('myCanvas');
const offscreen = canvas.transferControlToOffscreen();
const renderWorker = new Worker('/workers/renderer.js');
renderWorker.postMessage({ canvas: offscreen }, [offscreen]);
// workers/renderer.js
self.onmessage = (event) => {
const { canvas } = event.data;
const ctx = canvas.getContext('2d');
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Expensive drawing operations...
requestAnimationFrame(draw);
}
draw();
};
This is perfect for games and data visualizations — the rendering runs on a worker thread without competing with the main thread.
Worker Pool Pattern
Reuse workers instead of creating/destroying them:
class WorkerPool {
constructor(workerUrl, size = navigator.hardwareConcurrency || 4) {
this.workers = Array.from({ length: size }, () => ({
worker: new Worker(workerUrl),
busy: false,
}));
this.queue = [];
}
run(data) {
return new Promise((resolve, reject) => {
const task = { data, resolve, reject };
const freeWorker = this.workers.find(w => !w.busy);
if (freeWorker) {
this.dispatch(freeWorker, task);
} else {
this.queue.push(task);
}
});
}
dispatch(entry, task) {
entry.busy = true;
entry.worker.postMessage(task.data);
entry.worker.onmessage = (e) => {
task.resolve(e.data);
entry.busy = false;
if (this.queue.length > 0) {
this.dispatch(entry, this.queue.shift());
}
};
entry.worker.onerror = (e) => {
task.reject(e);
entry.busy = false;
};
}
}
// Usage
const pool = new WorkerPool('/workers/processor.js');
const results = await Promise.all(items.map(item => pool.run(item)));
Debugging Workers in Firefox
Firefox DevTools shows workers in the Debugger:
- Open DevTools → Debugger
- In the left panel, find your worker scripts under "Workers"
- Set breakpoints in worker code just like main thread code
- The worker's thread is listed separately in the "Threads" panel
- Each thread can be paused and stepped through independently
console.log() in workers outputs to the regular Browser Console in Firefox.
Top comments (0)