Web Workers are a powerful HTML5 feature that allows you to run JavaScript code in background threads, separate from the main execution thread. This prevents heavy computations from blocking the user interface, keeping your application responsive and smooth.
What are Web Workers?
Normally, JavaScript runs on a single thread, which means heavy calculations can freeze your webpage. Web Workers solve this by:
- Running JavaScript in separate background threads
- Performing computations without blocking the UI
- Enabling true parallel processing in web applications
- Communicating with the main thread through message passing
When to Use Web Workers
Web Workers are ideal for:
- Complex mathematical calculations
- Processing large datasets or arrays
- Image or video manipulation
- Parsing large JSON or XML files
- Text analysis or search operations
- Real-time data processing
- Cryptographic operations
Creating a Web Worker
To create a worker, you need two files: your main JavaScript file and a separate worker file.
Worker File (worker.js)
// This code runs in the worker thread
// Listen for messages from the main thread
self.addEventListener('message', function(e) {
console.log('Worker received:', e.data);
// Do some heavy computation
const result = performHeavyTask(e.data);
// Send result back to main thread
self.postMessage(result);
});
function performHeavyTask(data) {
// Simulate heavy computation
let result = 0;
for (let i = 0; i < data; i++) {
result += Math.sqrt(i);
}
return result;
}
// Alternative: Use onmessage instead of addEventListener
// onmessage = function(e) {
// const result = performHeavyTask(e.data);
// postMessage(result);
// };Main File
// Check if browser supports Web Workers
if (window.Worker) {
// Create a new worker
const myWorker = new Worker('worker.js');
// Send data to worker
myWorker.postMessage(1000000);
console.log('Message sent to worker');
// Listen for messages from worker
myWorker.addEventListener('message', function(e) {
console.log('Result from worker:', e.data);
});
// Handle errors
myWorker.addEventListener('error', function(e) {
console.error('Worker error:', e.message);
});
} else {
console.log('Web Workers are not supported in this browser');
}Complete Example
Here's a practical example showing how Web Workers prevent UI freezing:
<!DOCTYPE html>
<html>
<head>
<title>Web Worker Demo</title>
<style>
body {
font-family: Arial, sans-serif;
padding: 20px;
}
button {
padding: 10px 20px;
margin: 10px 5px;
font-size: 16px;
cursor: pointer;
}
#result {
margin-top: 20px;
padding: 15px;
background: #f0f9ff;
border-left: 4px solid #3b82f6;
}
.spinner {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid #f3f3f3;
border-top: 3px solid #3b82f6;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</head>
<body>
<h1>Web Worker Demo</h1>
<p>Try clicking this button while calculation is running:</p>
<button id="testBtn">Test UI Responsiveness</button>
<span id="clickCount">Clicks: 0</span>
<hr>
<button id="startWorker">Calculate with Worker (Non-blocking)</button>
<button id="startNormal">Calculate without Worker (Blocking)</button>
<div id="result"></div>
<script>
const testBtn = document.getElementById('testBtn');
const clickCount = document.getElementById('clickCount');
const startWorkerBtn = document.getElementById('startWorker');
const startNormalBtn = document.getElementById('startNormal');
const resultDiv = document.getElementById('result');
let clicks = 0;
// Test UI responsiveness
testBtn.addEventListener('click', () => {
clicks++;
clickCount.textContent = 'Clicks: ' + clicks;
});
// Heavy calculation function
function heavyCalculation(n) {
let result = 0;
for (let i = 0; i < n; i++) {
result += Math.sqrt(i) * Math.sin(i);
}
return result;
}
// Calculate with Web Worker (non-blocking)
startWorkerBtn.addEventListener('click', () => {
resultDiv.innerHTML = '<div class="spinner"></div> Calculating with worker...';
const worker = new Worker('calculation-worker.js');
worker.postMessage(50000000);
worker.addEventListener('message', (e) => {
resultDiv.innerHTML = ``;
worker.terminate(); // Clean up
});
worker.addEventListener('error', (e) => {
resultDiv.innerHTML = '<strong>Error:</strong> ' + e.message;
});
});
// Calculate without worker (blocking)
startNormalBtn.addEventListener('click', () => {
resultDiv.innerHTML = '<div class="spinner"></div> Calculating without worker...';
// Use setTimeout to allow UI to update
setTimeout(() => {
const result = heavyCalculation(50000000);
resultDiv.innerHTML = ``;
}, 100);
});
</script>
</body>
</html>// calculation-worker.js
self.addEventListener('message', function(e) {
const n = e.data;
// Perform heavy calculation
let result = 0;
for (let i = 0; i < n; i++) {
result += Math.sqrt(i) * Math.sin(i);
}
// Send result back
self.postMessage(result);
});Worker Scope and Limitations
Web Workers run in a different context and have limitations:
Available in Workers
self- reference to the worker itselfpostMessage()- send messages to main threadimportScripts()- load external scripts- XMLHttpRequest and fetch API for network requests
- Web Storage API (localStorage, sessionStorage)
- Timers (setTimeout, setInterval)
- WebSockets
NOT Available in Workers
- DOM manipulation (no access to document or window)
- Parent page objects
- Most browser APIs like alert(), confirm(), or prompt()
// In worker.js
// ✅ This works - importing external scripts
importScripts('utils.js', 'math-library.js');
// ✅ This works - making network requests
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => {
// Process data
self.postMessage(data);
});
// ✅ This works - using timers
setTimeout(() => {
self.postMessage('Delayed message');
}, 1000);
// ❌ This doesn't work - no DOM access
// document.getElementById('myDiv').textContent = 'Hello';
// ❌ This doesn't work - no window object
// window.alert('Hello');Terminating Workers
Always terminate workers when you're done to free up resources:
const worker = new Worker('worker.js');
// From main thread
worker.terminate();
// From inside the worker
self.close();Shared Workers
Shared Workers can be accessed by multiple scripts, even from different windows or tabs:
// shared-worker.js
let connections = 0;
self.addEventListener('connect', function(e) {
connections++;
const port = e.ports[0];
port.addEventListener('message', function(e) {
port.postMessage(`Connected clients: ${connections}`);
});
port.start();
});// Using a shared worker
const myWorker = new SharedWorker('shared-worker.js');
myWorker.port.addEventListener('message', function(e) {
console.log(e.data);
});
myWorker.port.start();
myWorker.port.postMessage('Hello');Inline Workers
You can create workers without separate files using Blob URLs:
// Create worker from inline code
const workerCode = `
self.addEventListener('message', function(e) {
const result = e.data * 2;
self.postMessage(result);
});
`;
const blob = new Blob([workerCode], { type: 'application/javascript' });
const workerUrl = URL.createObjectURL(blob);
const worker = new Worker(workerUrl);
worker.addEventListener('message', function(e) {
console.log('Result:', e.data);
});
worker.postMessage(5); // Result: 10
// Clean up
URL.revokeObjectURL(workerUrl);Error Handling
Always implement proper error handling for workers:
const worker = new Worker('worker.js');
// Listen for errors
worker.addEventListener('error', function(e) {
console.error('Worker error:', e);
console.error('Filename:', e.filename);
console.error('Line:', e.lineno);
console.error('Message:', e.message);
// Prevent error from bubbling
e.preventDefault();
});
// Alternatively, use onerror
worker.onerror = function(e) {
console.error('Worker failed:', e.message);
};Best Practices
- Check browser support: Always check if Web Workers are available
- Terminate when done: Free up resources by terminating workers
- Handle errors: Implement error handlers for robust applications
- Use for heavy tasks only: Workers have overhead; don't use for simple operations
- Minimize message passing: Transferring large data can be slow
- Keep workers simple: Workers should focus on computation, not complex logic
- Consider Transferable Objects: For large data, use transferable objects instead of copying
Transferable Objects
For better performance with large data, transfer ownership instead of copying:
// Create large array buffer
const buffer = new ArrayBuffer(1024 * 1024); // 1MB
// Transfer (move) instead of copy
worker.postMessage(buffer, [buffer]);
// After transfer, buffer is no longer usable in main thread
console.log(buffer.byteLength); // 0 - buffer was transferred
// In worker
self.addEventListener('message', function(e) {
const buffer = e.data;
console.log(buffer.byteLength); // 1048576 - full size
});Key Takeaways
- Web Workers run JavaScript in background threads without blocking the UI
- Use workers for heavy computations, data processing, and complex calculations
- Workers communicate with the main thread using postMessage() and message events
- Workers cannot access the DOM or window object
- Always terminate workers when done to free resources
- Shared Workers can be accessed from multiple scripts or tabs
- Use Transferable Objects for efficient large data transfer
What's Next?
You now understand how to use Web Workers to improve application performance through parallel processing. In the next lesson, we'll move into Web Accessibility, starting with an introduction to why accessibility matters for creating inclusive web experiences.