Web Worker in JavaScript: heavy work without freezing the page

A Web Worker runs JavaScript on a separate thread. The page stays responsive, but the worker never touches the DOM and speaks only through messages.
3 min read
Believemy logo

Sorting a hundred thousand rows, resizing an image, parsing a file on load: for those few seconds the page stops responding. Clicks pile up, animations freeze, the cursor spins.

The cause comes down to a simple rule: JavaScript runs one thing at a time inside a page. A single interface lets you step out of it.


Definition

A Web Worker is a script the browser runs in a separate thread, alongside the page. It shares no variable with it and communicates only through messages.

JAVASCRIPT
// page.js
const worker = new Worker("compute.js");

worker.postMessage({ rows: 5000000 });

worker.onmessage = (event) => {
  console.log(event.data.total);
  worker.terminate();
};
JAVASCRIPT
// compute.js
self.onmessage = (event) => {
  let total = 0;
  for (let i = 0; i < event.data.rows; i++) {
    total += i;
  }
  self.postMessage({ total });
};

Throughout the loop the page stays perfectly responsive. The self keyword refers to the worker itself instead of the window: this does not point at the same object as it does in a page.


What a worker does not have

Isolation comes at a price. A worker lives without a page, which takes away a good share of the usual tools.

  • No access to the DOM: no document, no querySelector(), not the slightest change to what is displayed.
  • No synchronous storage: localStorage does not exist in this context, unlike IndexedDB.
  • No shared variables: the worker sees nothing of what the page defined.

On the other hand fetch(), JSON, timers and RegExp expressions all work normally. The computation goes into the worker, and showing the result comes back to the page.


What travels inside a message

Data sent across is copied through the structured clone algorithm, not shared. Three consequences are worth knowing.

  • A function does not travel: the send fails with a cloning error.
  • A class instance loses its prototype: it arrives as a plain object, stripped of its methods.
  • A large buffer can be transferred rather than copied, by passing it as the second argument of postMessage.
Good to know

A worker is a file the browser loads, not an imported module. With a build tool, the expected form is new Worker(new URL("./compute.js", import.meta.url), { type: "module" }), otherwise the file is not picked up by the build.


Frequently asked questions

Question

Is async not enough to keep the page free?

No, and the confusion is common. An async function organizes the wait for a result, it adds no thread whatsoever. A heavy loop placed inside such a function blocks the page exactly as it would anywhere else, because it occupies the same thread, the one described by the Event loop.


Question

How many workers can I start?

Technically many, usefully few. Each one consumes its own memory and its own startup time, and past the number of cores on the machine they simply compete for the same resources. The navigator.hardwareConcurrency value gives an order of magnitude for sizing a pool of reused workers.


Question

How do I know a worker failed?

A worker never interrupts the page: an internal error stays locked inside it. The error event on the page-side object carries the message out, and messageerror reports data the cloning could not transport. Without those two listeners, a broken worker simply never answers.

Related terms

Discover our javaScript glossary

Every word of JavaScript explained simply: keywords, built-in objects, methods, errors and concepts. Clear definitions and examples that actually run, to learn and to troubleshoot.

Share this article

Want to help us? Share this article on your networks or even better: on your site, in an article or in your newsletter.