Problem Statement
What are Web Workers used for in JavaScript?
Explanation
Web Workers allow running JavaScript in background threads separate from the main UI thread.
They enable true parallelism for CPU-intensive tasks without blocking the UI.
Workers cannot access the DOM. They communicate with main thread via message passing.
Use workers for heavy computations, data processing, image manipulation, or encryption.
Workers have their own global scope, no access to window or document.
Understanding workers shows advanced knowledge of JavaScript performance optimization.
Code Solution
SolutionRead Only
// CREATING A WEB WORKER
// worker.js
self.addEventListener('message', (e) => {
const {data} = e;
// Heavy computation
const result = expensiveCalculation(data);
// Send result back
self.postMessage(result);
});
function expensiveCalculation(n) {
let sum = 0;
for (let i = 0; i < n; i++) {
sum += Math.sqrt(i);
}
return sum;
}
// USING THE WORKER (main thread)
const worker = new Worker('worker.js');
// Send data to worker
worker.postMessage(1000000);
// Receive result from worker
worker.addEventListener('message', (e) => {
console.log('Result:', e.data);
});
// Handle errors
worker.addEventListener('error', (e) => {
console.error('Worker error:', e.message);
});
// Terminate worker when done
worker.terminate();
// PRACTICAL EXAMPLE - Image processing
// imageWorker.js
self.addEventListener('message', (e) => {
const {imageData} = e.data;
// Process each pixel
for (let i = 0; i < imageData.data.length; i += 4) {
// Convert to grayscale
const avg = (
imageData.data[i] +
imageData.data[i + 1] +
imageData.data[i + 2]
) / 3;
imageData.data[i] = avg; // R
imageData.data[i + 1] = avg; // G
imageData.data[i + 2] = avg; // B
}
self.postMessage({imageData});
});
// Main thread
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const imageWorker = new Worker('imageWorker.js');
imageWorker.postMessage({imageData});
imageWorker.addEventListener('message', (e) => {
ctx.putImageData(e.data.imageData, 0, 0);
});
// DATA PROCESSING EXAMPLE
// dataWorker.js
self.addEventListener('message', (e) => {
const {data} = e;
// Process large dataset
const processed = data.map(item => ({
...item,
calculated: complexCalculation(item)
}));
self.postMessage(processed);
});
// Main thread
const dataWorker = new Worker('dataWorker.js');
fetch('/api/data')
.then(r => r.json())
.then(data => {
// Offload processing to worker
dataWorker.postMessage({data});
});
dataWorker.addEventListener('message', (e) => {
displayData(e.data);
});
// SHARED WORKERS (shared across tabs)
// sharedWorker.js
let connections = [];
self.addEventListener('connect', (e) => {
const port = e.ports[0];
connections.push(port);
port.addEventListener('message', (e) => {
// Broadcast to all connections
connections.forEach(conn => {
conn.postMessage(e.data);
});
});
port.start();
});
// Using shared worker
const sharedWorker = new SharedWorker('sharedWorker.js');
sharedWorker.port.addEventListener('message', (e) => {
console.log('Received:', e.data);
});
sharedWorker.port.start();
sharedWorker.port.postMessage('Hello');
// LIMITATIONS OF WEB WORKERS
// Cannot access:
// - DOM (document, window)
// - Parent page variables
// - Some browser APIs
// Can access:
// - navigator
// - location (read-only)
// - XMLHttpRequest
// - setTimeout/setInterval
// - importScripts()
// IMPORTING SCRIPTS IN WORKER
// worker.js
importScripts('library1.js', 'library2.js');
// Now can use functions from imported scripts
// TRANSFERABLE OBJECTS (avoid copying)
// Instead of copying large data, transfer ownership
const buffer = new ArrayBuffer(1024 * 1024); // 1MB
// Transfer (zero-copy)
worker.postMessage({buffer}, [buffer]);
// buffer is now unusable in main thread
// In worker
self.addEventListener('message', (e) => {
const {buffer} = e.data;
// Now worker owns the buffer
});
// WORKER POOL PATTERN
class WorkerPool {
constructor(workerScript, poolSize = 4) {
this.workers = [];
this.queue = [];
for (let i = 0; i < poolSize; i++) {
const worker = new Worker(workerScript);
worker.busy = false;
worker.addEventListener('message', (e) => {
worker.busy = false;
this.processQueue();
});
this.workers.push(worker);
}
}
execute(data) {
return new Promise((resolve, reject) => {
this.queue.push({data, resolve, reject});
this.processQueue();
});
}
processQueue() {
if (this.queue.length === 0) return;
const worker = this.workers.find(w => !w.busy);
if (!worker) return;
const task = this.queue.shift();
worker.busy = true;
worker.postMessage(task.data);
worker.onmessage = (e) => task.resolve(e.data);
worker.onerror = (e) => task.reject(e);
}
}
const pool = new WorkerPool('worker.js', 4);
// Use pool
pool.execute({data: 100}).then(result => {
console.log(result);
});