HTML Web Workers

JavaScript normally runs in a single thread — it does one thing at a time. When JavaScript runs a long, heavy task, it blocks everything else: buttons stop responding, animations freeze, and the page appears stuck. Web Workers solve this by running JavaScript code in a background thread, separate from the main page thread.

The Problem Web Workers Solve

Visual Diagram — Without Web Workers

Main Thread (only one thread):
┌──────┬──────────────────────────┬──────┐
│ Page │   Heavy task running...  │ Page │
│ runs │   (sorting 1M records)   │ runs │
└──────┴──────────────────────────┴──────┘
         ↑
    Page is FROZEN here — no clicks, no animations

With Web Workers (two threads):
Main Thread:  ┌──────┬──────────────────────────┬──────┐
              │ Page │ Page works fine!         │ Page │
              └──────┴──────────────────────────┴──────┘

Worker Thread:        ┌──────────────────────────┐
                      │   Heavy task running...  │
                      └──────────────────────────┘
Page stays responsive while the background work continues.

What Web Workers Can and Cannot Do

Web Workers CAN:
  ✓ Run JavaScript code
  ✓ Use fetch() to load data from URLs
  ✓ Use timers (setTimeout, setInterval)
  ✓ Use Web Storage (localStorage, sessionStorage)
  ✓ Do calculations, sort data, process large files
  ✓ Communicate with the main page via messages

Web Workers CANNOT:
  ✗ Access the DOM (no document.getElementById etc.)
  ✗ Access the window object
  ✗ Access the parent page's variables directly
  ✗ Display alerts or prompts

Workers live in isolation. They communicate with the main page only through messages — like passing notes through a door.

Creating a Web Worker

A Web Worker lives in a separate JavaScript file. The main page creates the worker by pointing to that file.

Step 1 — Create the Worker File (worker.js)

// worker.js
// This code runs in the background thread

self.addEventListener("message", function(event) {
  const input = event.data;           // receive data from main page

  // Do the heavy work here
  let result = 0;
  for (let i = 0; i < input; i++) {
    result += i;
  }

  self.postMessage(result);           // send result back to main page
});

Step 2 — Use the Worker in Your HTML Page

<button onclick="startWork()">Calculate Sum</button>
<p id="output">Result will appear here...</p>

<script>
  function startWork() {
    // Check browser support
    if (typeof Worker === "undefined") {
      document.getElementById("output").textContent = "Web Workers not supported";
      return;
    }

    // Create the worker
    const worker = new Worker("worker.js");

    // Send data to the worker
    worker.postMessage(10000000);   // ask it to sum numbers 0 to 9,999,999

    // Listen for the result
    worker.onmessage = function(event) {
      document.getElementById("output").textContent = "Sum: " + event.data;
      worker.terminate();           // stop the worker when done
    };

    // Handle errors
    worker.onerror = function(error) {
      console.log("Worker error:", error.message);
    };
  }
</script>

The Messaging System

Visual Diagram — Two-Way Communication

Main Page                           Worker (worker.js)
──────────────────────────────────────────────────────
worker.postMessage(data)  ──────→  event.data
                                   self.postMessage(result)
worker.onmessage = fn    ←──────   (sends result back)

Both sides use postMessage() to send.
Both sides use onmessage to receive.
Data travels as a copy — not a shared reference.

Sending Complex Data

You can send any data type that can be serialized: strings, numbers, booleans, arrays, objects. The worker receives a copy of the data — changes in the worker do not affect the original.

// Main page sends an object
worker.postMessage({
  task: "sort",
  data: [5, 2, 8, 1, 9, 3],
  order: "ascending"
});

// worker.js receives it
self.addEventListener("message", function(event) {
  const { task, data, order } = event.data;

  if (task === "sort") {
    const sorted = order === "ascending"
      ? data.sort((a, b) => a - b)
      : data.sort((a, b) => b - a);

    self.postMessage({ result: sorted });
  }
});

Transferable Objects — Faster Data Sharing

Normally, data sent between the page and a worker is copied. For large data like ArrayBuffer, this copying is slow. You can transfer ownership instead of copying — the original loses access to the data, but the transfer is instant.

// Main page — transfer a large buffer to the worker
const buffer = new ArrayBuffer(1024 * 1024);   // 1 MB
worker.postMessage(buffer, [buffer]);           // second argument lists transferables
// buffer is now empty/detached in main page

Dedicated vs Shared Workers

Dedicated Worker (most common)

A dedicated worker belongs to one page. Only that page can communicate with it. This is the type covered in the examples above — created with new Worker("file.js").

Shared Worker

A shared worker can be connected to by multiple browser tabs or windows from the same origin. It continues running as long as any connected tab remains open.

// Create a shared worker
const sharedWorker = new SharedWorker("shared-worker.js");

// Communicate through the port
sharedWorker.port.postMessage("Hello");
sharedWorker.port.onmessage = function(event) {
  console.log("Received:", event.data);
};
sharedWorker.port.start();

Terminating a Worker

Workers keep running until explicitly terminated. Always terminate a worker when its task is complete to free up memory and CPU.

// From the main page:
worker.terminate();

// From inside the worker itself:
self.close();

Practical Use Cases

Task                          Why Use a Worker
----------------------------  ------------------------------------------
Sorting 100,000 records       Would freeze page for several seconds
Image processing/filters      Heavy pixel calculations
Real-time data parsing        Parsing large JSON responses
Compression/encryption        CPU-intensive operations
Background data polling       Fetch data every 30s without blocking UI
Game physics calculations     Keep game loop smooth
Scientific simulations        Number-crunching without lag

Error Handling in Workers

// Catch errors in the worker from the main page:
worker.onerror = function(error) {
  console.log("Error in worker:");
  console.log("  Message:", error.message);
  console.log("  File:", error.filename);
  console.log("  Line:", error.lineno);
  error.preventDefault();   // prevent error from propagating further
};

// Catch unhandled errors inside the worker:
// worker.js
self.addEventListener("error", function(e) {
  console.log("Worker internal error:", e.message);
});

Browser Support

Web Workers are supported in all modern browsers including Chrome, Firefox, Safari, and Edge. They also work in mobile browsers on Android and iOS. For very old browsers (IE10 and below), you need to check for support with typeof Worker !== "undefined" before using them.

Web Workers bring multi-threaded thinking to the browser. They are the correct tool whenever a JavaScript task takes more than 50–100 milliseconds — anything longer starts to make the page feel sluggish to users.

Leave a Comment

Your email address will not be published. Required fields are marked *