Academic Block

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");

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");

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");

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);

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 thread
  • postMessage / onmessage for communication
  • terminate() 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.

📝 JavaScript Code
👁 Console Output

Click “Run Code” to see the output here.

Ctrl + Enter to run code Esc to close