Web Workers
This lesson explains Web Workers with clear examples, use cases, and best practices so you can use HTML5 APIs confidently in real web applications.
HTML5 Web Workers Overview
The HTML5 Web Workers API allows JavaScript to run scripts in
background threads separate from the main browser UI thread. This
keeps the page responsive while heavy calculations, data parsing,
file processing, and other CPU-intensive tasks run in parallel.
Web Workers communicate with the main page through message passing
using postMessage() and the message event.
They cannot directly access the DOM, but they can process data,
perform network requests, and return results to the main thread for
UI updates.
| Feature | Description |
| API Name | Web Workers API |
| Main Object | Worker |
| Thread Type | Background JavaScript thread |
| Communication | postMessage() and message events |
| DOM Access | Not allowed inside workers |
| Best Use | CPU-heavy work without blocking the UI |
Basic Web Worker Example
// main.js
if ("Worker" in window) {
const worker =
new Worker("/workers/example-worker.js");
worker.postMessage(
{
type: "GREET",
name: "Developer"
}
);
worker.addEventListener("message", (event) => {
console.log("Worker says:", event.data.message);
});
worker.addEventListener("error", (error) => {
console.error("Worker error:", error.message);
});
} else {
console.log("Web Workers are not supported.");
}
// example-worker.js
self.addEventListener("message", (event) => {
if (event.data.type === "GREET") {
self.postMessage(
{
type: "GREETING",
message: `Hello, ${event.data.name}!`
}
);
}
});
The main script creates a worker, sends a message, and listens for
the response. The worker script runs in a separate thread and
replies using self.postMessage().
How Web Workers Work
Web Workers use a message-based communication model. The main
thread and worker thread do not share variables or DOM access
directly. Data is copied between threads using the structured
clone algorithm.
| Step | Description | Example Syntax |
| Create Worker | Start a background thread with a worker script. | new Worker("worker.js") |
| Send Message | Pass data from main thread to worker. | worker.postMessage(data) |
| Listen for Reply | Receive processed results on the main thread. | worker.addEventListener("message", callback) |
| Process in Worker | Run heavy logic in the background thread. | self.addEventListener("message", callback) |
| Return Result | Send data back to the main thread. | self.postMessage(result) |
| Terminate Worker | Stop the worker when work is complete. | worker.terminate() |
Types of Web Workers
| Worker Type | Description | Typical Use |
| Dedicated Worker | Owned by a single script that created it. | Heavy calculations, parsing, image processing. |
| Shared Worker | Shared across multiple tabs or browsing contexts. | Shared state, cross-tab communication. |
| Service Worker | Acts as a network proxy for offline and caching. | Progressive Web Apps, push notifications. |
| Module Worker | Worker that supports ES module import. | Organized worker code with shared utilities. |
What Web Workers Can and Cannot Access
| Allowed in Workers | Not Allowed in Workers |
| JavaScript calculations and loops | Direct DOM manipulation |
fetch() and network requests | document and page elements |
setTimeout() and setInterval() | Most window UI APIs |
| Data parsing and transformation | Direct CSS or layout access |
| File, Blob, and ArrayBuffer processing | Direct event listeners on page elements |
| Importing scripts or modules | Synchronous cross-thread variable sharing |
Offload a Heavy Calculation
// main.js
const worker =
new Worker("/workers/sum-worker.js");
const numbers =
Array.from(
{ length: 1000000 },
(_, index) => index + 1
);
worker.postMessage(
{
type: "SUM",
numbers
}
);
worker.addEventListener("message", (event) => {
if (event.data.type === "SUM_COMPLETE") {
console.log("Total:", event.data.total);
worker.terminate();
}
});
// sum-worker.js
self.addEventListener("message", (event) => {
if (event.data.type === "SUM") {
const total =
event.data.numbers.reduce(
(sum, value) => sum + value,
0
);
self.postMessage(
{
type: "SUM_COMPLETE",
total
}
);
}
});
Summing a large array on the main thread can freeze the UI. Moving
the calculation into a worker keeps scrolling, typing, and
animations smooth while the result is computed in the background.
Handle Worker Errors
const worker =
new Worker("/workers/task-worker.js");
worker.addEventListener("message", (event) => {
console.log("Success:", event.data);
});
worker.addEventListener("error", (event) => {
console.error(
"Worker failed:",
event.message,
"at",
event.filename,
event.lineno
);
});
worker.addEventListener("messageerror", () => {
console.error("Message could not be cloned.");
});
worker.postMessage(
{
type: "PROCESS",
payload: "safe-data"
}
);
Always listen for error and messageerror
events. Worker scripts can fail to load, throw runtime errors, or
receive data that cannot be cloned between threads.
Transfer Large Binary Data
// main.js
const worker =
new Worker("/workers/image-worker.js");
const buffer =
new ArrayBuffer(1024 * 1024);
worker.postMessage(
{
type: "PROCESS_IMAGE",
buffer
},
[buffer]
);
// buffer is now detached on the main thread
// image-worker.js
self.addEventListener("message", (event) => {
const view =
new Uint8Array(event.data.buffer);
for (let i = 0; i < view.length; i += 1) {
view[i] = view[i] ^ 255;
}
self.postMessage(
{
type: "IMAGE_PROCESSED",
buffer: event.data.buffer
},
[event.data.buffer]
);
});
For large binary data such as images or files, use transferable
objects in the second argument of postMessage(). This
moves ownership instead of copying memory, which improves
performance significantly.
Feature Detection
function supportsWebWorkers() {
return typeof Worker !== "undefined";
}
if (supportsWebWorkers()) {
console.log("Web Workers are supported.");
} else {
console.log("Web Workers are not supported.");
// Run fallback logic on the main thread
}
Feature detection is important because worker support can vary by
browser, security policy, and environment. Always provide a
fallback when workers are unavailable.
Common Web Worker Methods and Events
| Method / Event | Where Used | Purpose |
new Worker() | Main Thread | Creates a new background worker. |
postMessage() | Main Thread / Worker | Sends data between threads. |
message | Main Thread / Worker | Receives messages from the other side. |
error | Main Thread | Handles worker script failures. |
messageerror | Main Thread / Worker | Handles non-cloneable message data. |
terminate() | Main Thread | Stops the worker immediately. |
self.close() | Worker | Allows the worker to stop itself. |
Common Web Worker Use Cases
- Large dataset filtering, sorting, and aggregation.
- Image resizing, compression, and pixel processing.
- CSV, JSON, XML, and log file parsing.
- Mathematical simulations, statistics, and encryption.
- Building client-side search indexes.
- Transforming API responses before rendering UI.
- Background compression and decompression tasks.
Web Worker Best Practices
- Use workers for CPU-heavy tasks, not simple UI updates.
- Always check for worker support before creating one.
- Keep message payloads small when possible.
- Use transferable objects for large binary data.
- Terminate workers when they are no longer needed.
- Handle
error and messageerror events. - Do not access the DOM directly from inside a worker.
- Design clear message types so communication stays predictable.
Common Web Worker Mistakes
- Trying to access
document or DOM elements inside a worker. - Assuming workers share variables directly with the main thread.
- Forgetting to terminate workers after completing a task.
- Not handling worker load failures or runtime errors.
- Using workers for small tasks that do not benefit from threading.
- Sending non-cloneable objects such as functions through
postMessage(). - Blocking the worker with infinite loops or unbounded work.
Web Workers vs Main Thread
| Feature | Web Workers | Main Thread |
| DOM Access | Not available. | Full DOM and UI control. |
| Best For | Heavy calculations and background processing. | Rendering, events, and user interactions. |
| Communication | Message passing with postMessage(). | Direct function calls and DOM updates. |
| UI Impact | Does not block the UI when used correctly. | Long tasks can freeze the page. |
Key Takeaways
- Web Workers run JavaScript in background threads separate from the UI.
- Use
new Worker() to create a worker and postMessage() to communicate. - Workers cannot access the DOM, but they excel at data processing and heavy computation.
- Feature detection, error handling, and worker termination are essential in production.
- Transferable objects improve performance when moving large binary data between threads.
Pro Tip
Start with a simple worker that receives a message and returns a
result. Once that works, move real heavy tasks such as parsing,
filtering, or image processing into the worker and keep DOM updates
on the main thread.