Lesson 0002 · ~12 minutes
One skill: design a correct dedicated-worker loop — create, message, handle errors, shut down — without treating it like a shared-memory thread.
You can sketch a component-safe worker client (mount → postMessage → onmessage → terminate) and explain copy-not-share messaging in a design review.
A
Worker
is a handle on the main thread to a script running in
DedicatedWorkerGlobalScope. Per
MDN
and the
WHATWG intro:
new Worker(url) (optionally
{ type: "module" }, { name }).
postMessage / message events.
Spec non-normative guidance that matters for design: workers are relatively heavy (startup + memory). Prefer long-lived workers you reuse, not one worker per pixel or per keystroke.
MDN’s crucial note: on the main thread you hang
postMessage / onmessage on the
Worker object; inside the worker they live
on the global scope, because the worker is the global.
// main.js
const worker = new Worker("worker.js");
worker.postMessage({ type: "sum", a: 40, b: 2 });
worker.onmessage = (e) => {
console.log(e.data); // { type: "sum", result: 42 }
};
worker.onerror = (e) => {
console.error(e.message, e.filename, e.lineno);
};
// later / unmount
worker.terminate();
// worker.js
onmessage = (e) => {
const { type, a, b } = e.data;
if (type === "sum") {
postMessage({ type: "sum", result: a + b });
}
};
// optional: worker decides it's done
// close();
The platform is fire-and-forget events, not RPC. Once you send more
than one kind of job, put a type (and usually a
correlation id) on every message so the main thread can
match answers to waiting UI work. Libraries like Comlink hide this;
you should still understand the wire shape.
Browser fetches the worker script, spins a thread + global scope.
Failures (e.g. bad URL / cross-origin policy) surface as
error on the Worker.
Either side postMessages. Receiver runs
onmessage. Payload is a clone (unless you transfer —
later lesson).
Worker runs long tasks without blocking main-thread paint/input.
Main thread only updates DOM from onmessage.
Runtime errors in the worker fire error on the main
Worker (message, filename,
lineno). Also listen for messageerror when
a message cannot be deserialized.
Main: terminate() — immediate kill.
Worker: close() — discard queued tasks, stop the scope.
SPA unmount → terminate (and drop references).
| Action | Who calls | Use when |
|---|---|---|
terminate() |
Main thread | Component unmount, cancel job hard, abandon hung work (MDN: does not let the worker finish) |
close() |
Inside worker | Worker finished its purpose and can self-retire cleanly |
| Drop all refs | Main thread | After terminate (or natural end) so the handle can be GC’d |
terminate on unmount → zombie threads across route
changes.
postMessage does not change the worker’s copy.
onerror means
silent failure in production.
Drag is optional; click the steps in the order a healthy SPA client should use them. Immediate feedback.
Click steps in correct order (1 → 5). Wrong picks reset.
Equal-length options. Pick the best default for each review comment.
React route unmounts a page that owns a long-running CSV worker. What should cleanup do?
Main thread does obj.x = 1; worker.postMessage(obj); obj.x =
2; What does the worker see for x?
Photo app applies filters often. Team proposes
new Worker on every button click.
Design review: “How do we know the worker script failed to load or threw?”
worker.postMessage / onmessage /
onerror / terminate().
postMessage / onmessage /
close().
MDN — Using Web Workers (dedicated section: spawn, messages, terminate, errors). Skim the WHATWG tutorial: creating & communicating for the normative short path.