Page 3

March 5, 20264 min read
service workerPWAofflinecachingweb workersprogressive web appsbackground sync
var worker = new Worker('./worker.js');

That one line does everything:

  1. Browser fetches worker.js from the server
  2. Spins up a new thread
  3. Starts running that file on it

The Only Thing You Need to Get Right — The Path

new Worker('./worker.js') // same folder as current page new Worker('/js/worker.js') // absolute path from server root new Worker('../workers/fib.js') // relative path going up a folder

Same rules as any fetch() or <script src="...">. Just point it at the right file.


What You Get Back

The new Worker() call immediately returns a worker object — your handle to communicate with that thread:

var worker = new Worker('./worker.js'); worker.postMessage(...) // send data TO the worker worker.addEventListener('message') // receive data FROM the worker worker.terminate() // kill the thread

That's the entire API for talking to a worker. Three things. That's it.

No, postMessage does not close the thread. It just sends data — the thread stays alive after.

var start = Date.now(); var answer = fib(42); // 1. calculate var ms = Date.now() - start; self.postMessage({ answer, ms }); // 2. send result — thread still running // 3. nothing left to do, so it goes idle

The thread closes because the script ran out of code to execute — not because of postMessage.


The Difference

// worker.js — sends one message then goes idle (nothing left to run) self.postMessage('done'); // ↑ thread is now idle but technically still alive until terminate() // worker.js — sends many messages, stays busy function next() { self.postMessage(fib(n++)); setTimeout(next, 0); // keeps scheduling more work → stays alive } next();

Three Ways a Worker Actually Closes

// 1. From the PAGE — forceful, immediate worker.terminate(); // 2. From INSIDE the worker — graceful self-close self.close(); // 3. Naturally — worker script finishes and has no pending timers/listeners // browser garbage collects it eventually

In example 02, after self.postMessage(...) there's no more code, no setTimeout, no event listeners — so the worker goes idle naturally. But in example 03, setTimeout(next, 0) keeps scheduling more work, which is why you need terminate() to stop it.

Two sides of the same communication channel — direction is the only difference:

PAGE (main.js) WORKER (worker.js) ───────────────────────────────────────────────────── worker.postMessage() ───► self.addEventListener('message') worker.addEventListener('message') ◄─── self.postMessage()

worker.postMessage() — Page talks TO the worker

// main.js — you have the worker object, so you use it worker.postMessage({ stop: true });

self.postMessage() — Worker talks TO the page

// worker.js — you don't have a "page" object, so you use self self.postMessage({ n: 42, result: 999 });

The reason for self

Inside a worker there is no window, no document, no reference to the page. The only thing the worker knows about itself is self. So:

// main.js → has window, document, worker object // worker.js → has only self

self in a worker is exactly like window in a page — it's the global scope of that thread.


Symmetric Pattern

// main.js worker.postMessage('ping'); worker.addEventListener('message', evt => console.log(evt.data)); // receives 'pong' // worker.js self.addEventListener('message', evt => { if (evt.data === 'ping') self.postMessage('pong'); });

Same postMessage + addEventListener('message') on both sides — just different objects (worker vs self).

Yes, exactly. terminate() doesn't wait in any queue — it's an outside kill, not a message.

worker.postMessage({ stop: true }) → goes into the message QUEUE worker reads it when it's free worker.terminate() → bypasses everything thread dies RIGHT NOW

Why the Priority Difference Exists

postMessage is cooperative — it asks the worker to stop itself. The worker has to:

  1. Finish current calculation
  2. Return to event loop
  3. Read the queue
  4. See the stop message
  5. Call self.close()

terminate() is forceful — the browser kills the OS thread directly. No cooperation needed, no queue checked, no code runs.

// This can be ignored if worker is busy: worker.postMessage({ stop: true }); // "please stop when you get a chance" // This cannot be ignored, ever: worker.terminate(); // "you are dead right now"

The Analogy

Think of it like a running program:

postMessage({ stop: true }) = asking someone nicely to stop working terminate() = pulling the power cable out of the computer

One depends on the other person cooperating. The other doesn't ask.