Web Workers in JavaScript
Web Workers provide a way to run JavaScript in background threads, separate from the main execution thread. This enables concurrent processing without blocking the user interface.
Basic Concept
JavaScript traditionally runs in a single thread. Web Workers allow CPU-intensive tasks to run in the background:
// main.js - Main thread
const worker = new Worker("worker.js");
// Send data to the worker
worker.postMessage({ data: [1, 2, 3, 4, 5] });
// Receive messages from the worker
worker.onmessage = function(event) {
console.log("Result from worker:", event.data);
};
// Handle errors
worker.onerror = function(error) {
console.error("Worker error:", error.message);
};
// Terminate when done
function stopWorker() {
worker.terminate();
}
// worker.js - Worker thread
self.onmessage = function(event) {
const { data } = event.data;
// Perform computation without blocking the UI
const result = data.map(x => x * x).reduce((a, b) => a + b, 0);
// Send the result back to the main thread
self.postMessage({ result });
};
Types of Web Workers
// 1. Dedicated Workers - Used by a single script
const dedicatedWorker = new Worker("worker.js");
// 2. Shared Workers - Accessible by multiple scripts
const sharedWorker = new SharedWorker("shared-worker.js");
sharedWorker.port.start();
sharedWorker.port.postMessage({ action: "getData" });
// 3. Service Workers - Special type for offline capabilities
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js');
}
Data Transfer
// Basic data transfer
worker.postMessage({ command: 'process', data: [1, 2, 3] });
// Transferable Objects - Transfer ownership for large data
const buffer = new ArrayBuffer(100 * 1024 * 1024); // 100MB buffer
worker.postMessage({ buffer }, [buffer]); // Second argument is transfer list
// Structured Cloning - Automatic for most data types
worker.postMessage({
primitives: { number: 42, string: "Hello" },
arrays: [1, 2, [3, 4]],
date: new Date(),
map: new Map([["key", "value"]])
});
Key Use Cases
1. CPU-Intensive Calculations
// Prime number calculation
function findPrimes(max) {
const worker = new Worker("prime-worker.js");
worker.postMessage({ max });
worker.onmessage = function(e) {
console.log(`Found ${e.data.primes.length} prime numbers`);
displayResults(e.data.primes);
worker.terminate();
};
}
2. Image Processing
// Image filters, compression, analysis
function processImage(imageData) {
const worker = new Worker("image-worker.js");
worker.postMessage({
width: imageData.width,
height: imageData.height,
data: imageData.data.slice(0)
});
worker.onmessage = function(e) {
updateCanvas(e.data);
worker.terminate();
};
}
3. Data Processing and Analysis
// Processing large datasets
function analyzeData(dataset) {
const worker = new Worker("data-worker.js");
worker.postMessage({ dataset });
worker.onmessage = function(e) {
const { mean, median, stdDev } = e.data;
updateUI(mean, median, stdDev);
worker.terminate();
};
}
4. Parallel Processing
// Distribute work across multiple workers
function processInParallel(data, workerCount = 4) {
const chunkSize = Math.ceil(data.length / workerCount);
const results = [];
let completedWorkers = 0;
return new Promise((resolve) => {
for (let i = 0; i < workerCount; i++) {
const chunk = data.slice(i * chunkSize, (i + 1) * chunkSize);
const worker = new Worker("worker.js");
worker.postMessage({ chunk, workerId: i });
worker.onmessage = function(e) {
results[e.data.workerId] = e.data.result;
completedWorkers++;
worker.terminate();
if (completedWorkers === workerCount) {
resolve(results.flat());
}
};
}
});
}
Limitations
// 1. No DOM access
// worker.js
// document.querySelector('div'); // Error: document is not defined
// 2. Limited window object access
// console.log is available, but most window properties are not
// 3. Same-origin policy
// Can only load scripts from the same origin
// 4. Overhead
// Each worker has memory cost, not suitable for small tasks
Best Practices
// 1. Use for CPU-intensive tasks only
// Not worth the overhead for simple operations
// 2. Terminate workers when done
worker.terminate();
// Or self-terminate
self.close();
// 3. Handle errors
worker.onerror = function(error) {
console.error("Worker error:", error.message);
};
// 4. Use transferable objects for large data
worker.postMessage({ buffer }, [buffer]);
// 5. Consider a worker pool for frequent tasks
class WorkerPool {
constructor(size) {
this.workers = [];
this.queue = [];
for (let i = 0; i < size; i++) {
this.workers.push(new Worker("worker.js"));
}
}
// Process task queue
processTask(data) {
// Implementation details...
}
}
Interview Tips
- Explain that Web Workers enable true parallel execution in JavaScript
- Describe the difference between Dedicated Workers, Shared Workers, and Service Workers
- Explain how data is transferred between threads (structured cloning vs. transferable objects)
- Discuss appropriate use cases: CPU-intensive tasks, not simple operations
- Mention limitations: no DOM access, separate memory context, communication overhead
- Describe how to implement a worker pool for efficient resource management
Test Your Knowledge
Take a quick quiz to test your understanding of this topic.