Web Workers Get Certified
JavaScript normally runs on a single thread, which means a long, heavy computation can freeze the entire page. The Web Workers API lets you run a script on a separate background thread so the main thread stays responsive.
In this lesson you will learn how to create a worker, send and receive messages with postMessage() and onmessage, handle errors, stop a worker with terminate(), and understand what a worker can and cannot access.
Creating a Worker
What this code does: Creates a mock worker that can receive messages and return results.
function createMockWorker(handler) {
let onmessageCallback = null;
let terminated = false;
return {
set onmessage(fn) { onmessageCallback = fn; },
postMessage(data) {
if (terminated) return;
const result = handler(data);
if (onmessageCallback) onmessageCallback({ data: result });
},
terminate() {
terminated = true;
console.log("Worker terminated");
}
};
}
const worker = createMockWorker(data => data * 2);
console.log("Worker created and ready");
worker.onmessage = fn.{ data: result } to onmessage — same shape as the real API.Sending and Receiving Messages
What this code does: Sends data to the worker and logs the result received back.
function createMockWorker(handler) {
let onmessageCallback = null;
return {
set onmessage(fn) { onmessageCallback = fn; },
postMessage(data) {
const result = handler(data);
if (onmessageCallback) onmessageCallback({ data: result });
}
};
}
const worker = createMockWorker(data => "Processed: " + data);
worker.onmessage = event => {
console.log("Main thread received:", event.data);
};
console.log("Sending message...");
worker.postMessage("Hello, worker!");
console.log("Message sent — main thread continues");
event.data holds the value the worker sent back.Offloading a Heavy Calculation
What this code does: Runs a heavy sum in the worker so the main thread stays free.
function createMockWorker(handler) {
let onmessageCallback = null;
return {
set onmessage(fn) { onmessageCallback = fn; },
postMessage(data) {
const result = handler(data);
if (onmessageCallback) onmessageCallback({ data: result });
}
};
}
function sumRange(n) {
let total = 0;
for (let i = 1; i <= n; i++) total += i;
return total;
}
const worker = createMockWorker(n => sumRange(n));
worker.onmessage = event => console.log("Sum result:", event.data);
console.log("Starting calculation in background...");
worker.postMessage(1000000);
console.log("Main thread is free");
Terminating a Worker
What this code does: Shows that a terminated worker ignores any further messages.
function createMockWorker(handler) {
let onmessageCallback = null;
let terminated = false;
return {
set onmessage(fn) { onmessageCallback = fn; },
postMessage(data) {
if (terminated) {
console.log("Message ignored — worker already terminated");
return;
}
const result = handler(data);
if (onmessageCallback) onmessageCallback({ data: result });
},
terminate() {
terminated = true;
console.log("Worker terminated");
}
};
}
const worker = createMockWorker(data => data.toUpperCase());
worker.onmessage = event => console.log("Result:", event.data);
worker.postMessage("hello");
worker.terminate();
worker.postMessage("this will be ignored");
Handling Worker Errors
What this code does: Catches and logs an error thrown inside the worker.
function createMockWorker(handler) {
let onmessageCallback = null;
let onerrorCallback = null;
return {
set onmessage(fn) { onmessageCallback = fn; },
set onerror(fn) { onerrorCallback = fn; },
postMessage(data) {
try {
const result = handler(data);
if (onmessageCallback) onmessageCallback({ data: result });
} catch (err) {
if (onerrorCallback) onerrorCallback({ message: err.message });
}
}
};
}
const worker = createMockWorker(data => {
if (typeof data !== "number") throw new Error("Worker expected a number");
return data * 2;
});
worker.onmessage = event => console.log("Result:", event.data);
worker.onerror = error => console.log("Worker error:", error.message);
console.log("Sending invalid data...");
worker.postMessage("not a number");
worker.onerror = fn.Sending Multiple Messages (Progress)
What this code does: Worker sends progress updates and a final “done” message.
function createProgressWorker() {
let onmessageCallback = null;
return {
set onmessage(fn) { onmessageCallback = fn; },
start(total) {
for (let i = 1; i <= total; i++) {
if (onmessageCallback) {
onmessageCallback({ data: { type: "progress", completed: i, total } });
}
}
if (onmessageCallback) onmessageCallback({ data: { type: "done" } });
}
};
}
const worker = createProgressWorker();
worker.onmessage = event => {
if (event.data.type === "progress") {
console.log("Progress:", event.data.completed + "/" + event.data.total);
} else {
console.log("Task complete!");
}
};
console.log("Starting progress task...");
worker.start(3);
{ type, completed, total } — structured so the main thread can branch.{ type: "done" } signals completion.What Workers Can and Cannot Access
| Feature | Main Thread | Worker Thread |
|---|---|---|
| Access the DOM | Yes | No |
| window object | Yes | No (has self) |
| fetch / XHR | Yes | Yes |
| Can freeze the page? | Yes | No |
Common Mistakes
- Trying to access the DOM from inside a worker
- Assuming postMessage shares a live reference (it copies)
- Forgetting to call terminate()
- Missing an onerror handler
Best Practices
- Use workers only for CPU-heavy work
- Always add onerror
- Call terminate() when finished
- Keep messages simple and serializable
Quick Summary
new Worker("file.js")creates a background threadpostMessage/onmessagefor communicationterminate()stops the worker- Workers cannot access the DOM
Try It Yourself
What this code does: Sums an array in a worker and handles invalid input.
function createMockWorker(handler) {
let onmessageCallback = null;
let onerrorCallback = null;
let terminated = false;
return {
set onmessage(fn) { onmessageCallback = fn; },
set onerror(fn) { onerrorCallback = fn; },
postMessage(data) {
if (terminated) {
console.log("Ignored — worker already terminated");
return;
}
try {
const result = handler(data);
if (onmessageCallback) onmessageCallback({ data: result });
} catch (err) {
if (onerrorCallback) onerrorCallback({ message: err.message });
}
},
terminate() {
terminated = true;
console.log("Worker terminated");
}
};
}
const worker = createMockWorker(numbers => {
if (!Array.isArray(numbers)) throw new Error("Expected an array of numbers");
return numbers.reduce((t, n) => t + n, 0);
});
worker.onmessage = e => console.log("Total:", e.data);
worker.onerror = e => console.log("Error:", e.message);
console.log("Sending numbers...");
worker.postMessage([1, 2, 3, 4, 5]);
Tip: Use workers for real CPU-heavy work, always handle errors, and terminate them when done.
🧪 Test Your JavaScript Code
Edit the code on the left and click “Run Code” to see the console output on the right.
Click “Run Code” to see the output here.
Code Explanation
The complete example stays visible. Use Previous and Next to move through its logical blocks.
Select Next to begin exploring the code.