Skip to content

Web Workers

This lesson explains Web Workers with clear examples, use cases, and best practices so you can use HTML5 APIs confidently in real web applications.

HTML5 Web Workers Overview

The HTML5 Web Workers API allows JavaScript to run scripts in background threads separate from the main browser UI thread. This keeps the page responsive while heavy calculations, data parsing, file processing, and other CPU-intensive tasks run in parallel.

Web Workers communicate with the main page through message passing using postMessage() and the message event. They cannot directly access the DOM, but they can process data, perform network requests, and return results to the main thread for UI updates.

Feature Description
API Name Web Workers API
Main Object Worker
Thread Type Background JavaScript thread
Communication postMessage() and message events
DOM Access Not allowed inside workers
Best Use CPU-heavy work without blocking the UI

Basic Web Worker Example

// main.js

if ("Worker" in window) {
  const worker =
    new Worker("/workers/example-worker.js");

  worker.postMessage(
    {
      type: "GREET",
      name: "Developer"
    }
  );

  worker.addEventListener("message", (event) => {
    console.log("Worker says:", event.data.message);
  });

  worker.addEventListener("error", (error) => {
    console.error("Worker error:", error.message);
  });
} else {
  console.log("Web Workers are not supported.");
}
// example-worker.js

self.addEventListener("message", (event) => {
  if (event.data.type === "GREET") {
    self.postMessage(
      {
        type: "GREETING",
        message: `Hello, ${event.data.name}!`
      }
    );
  }
});

The main script creates a worker, sends a message, and listens for the response. The worker script runs in a separate thread and replies using self.postMessage().

How Web Workers Work

Web Workers use a message-based communication model. The main thread and worker thread do not share variables or DOM access directly. Data is copied between threads using the structured clone algorithm.

Step Description Example Syntax
Create Worker Start a background thread with a worker script. new Worker("worker.js")
Send Message Pass data from main thread to worker. worker.postMessage(data)
Listen for Reply Receive processed results on the main thread. worker.addEventListener("message", callback)
Process in Worker Run heavy logic in the background thread. self.addEventListener("message", callback)
Return Result Send data back to the main thread. self.postMessage(result)
Terminate Worker Stop the worker when work is complete. worker.terminate()

Types of Web Workers

Worker Type Description Typical Use
Dedicated Worker Owned by a single script that created it. Heavy calculations, parsing, image processing.
Shared Worker Shared across multiple tabs or browsing contexts. Shared state, cross-tab communication.
Service Worker Acts as a network proxy for offline and caching. Progressive Web Apps, push notifications.
Module Worker Worker that supports ES module import. Organized worker code with shared utilities.

What Web Workers Can and Cannot Access

Allowed in Workers Not Allowed in Workers
JavaScript calculations and loops Direct DOM manipulation
fetch() and network requests document and page elements
setTimeout() and setInterval() Most window UI APIs
Data parsing and transformation Direct CSS or layout access
File, Blob, and ArrayBuffer processing Direct event listeners on page elements
Importing scripts or modules Synchronous cross-thread variable sharing

Offload a Heavy Calculation

// main.js

const worker =
  new Worker("/workers/sum-worker.js");

const numbers =
  Array.from(
    { length: 1000000 },
    (_, index) => index + 1
  );

worker.postMessage(
  {
    type: "SUM",
    numbers
  }
);

worker.addEventListener("message", (event) => {
  if (event.data.type === "SUM_COMPLETE") {
    console.log("Total:", event.data.total);
    worker.terminate();
  }
});
// sum-worker.js

self.addEventListener("message", (event) => {
  if (event.data.type === "SUM") {
    const total =
      event.data.numbers.reduce(
        (sum, value) => sum + value,
        0
      );

    self.postMessage(
      {
        type: "SUM_COMPLETE",
        total
      }
    );
  }
});

Summing a large array on the main thread can freeze the UI. Moving the calculation into a worker keeps scrolling, typing, and animations smooth while the result is computed in the background.

Handle Worker Errors

const worker =
  new Worker("/workers/task-worker.js");

worker.addEventListener("message", (event) => {
  console.log("Success:", event.data);
});

worker.addEventListener("error", (event) => {
  console.error(
    "Worker failed:",
    event.message,
    "at",
    event.filename,
    event.lineno
  );
});

worker.addEventListener("messageerror", () => {
  console.error("Message could not be cloned.");
});

worker.postMessage(
  {
    type: "PROCESS",
    payload: "safe-data"
  }
);

Always listen for error and messageerror events. Worker scripts can fail to load, throw runtime errors, or receive data that cannot be cloned between threads.

Transfer Large Binary Data

// main.js

const worker =
  new Worker("/workers/image-worker.js");

const buffer =
  new ArrayBuffer(1024 * 1024);

worker.postMessage(
  {
    type: "PROCESS_IMAGE",
    buffer
  },
  [buffer]
);

// buffer is now detached on the main thread
// image-worker.js

self.addEventListener("message", (event) => {
  const view =
    new Uint8Array(event.data.buffer);

  for (let i = 0; i < view.length; i += 1) {
    view[i] = view[i] ^ 255;
  }

  self.postMessage(
    {
      type: "IMAGE_PROCESSED",
      buffer: event.data.buffer
    },
  [event.data.buffer]
  );
});

For large binary data such as images or files, use transferable objects in the second argument of postMessage(). This moves ownership instead of copying memory, which improves performance significantly.

Feature Detection

function supportsWebWorkers() {
  return typeof Worker !== "undefined";
}

if (supportsWebWorkers()) {
  console.log("Web Workers are supported.");
} else {
  console.log("Web Workers are not supported.");
  // Run fallback logic on the main thread
}

Feature detection is important because worker support can vary by browser, security policy, and environment. Always provide a fallback when workers are unavailable.

Common Web Worker Methods and Events

Method / Event Where Used Purpose
new Worker() Main Thread Creates a new background worker.
postMessage() Main Thread / Worker Sends data between threads.
message Main Thread / Worker Receives messages from the other side.
error Main Thread Handles worker script failures.
messageerror Main Thread / Worker Handles non-cloneable message data.
terminate() Main Thread Stops the worker immediately.
self.close() Worker Allows the worker to stop itself.

Common Web Worker Use Cases

  • Large dataset filtering, sorting, and aggregation.
  • Image resizing, compression, and pixel processing.
  • CSV, JSON, XML, and log file parsing.
  • Mathematical simulations, statistics, and encryption.
  • Building client-side search indexes.
  • Transforming API responses before rendering UI.
  • Background compression and decompression tasks.

Web Worker Best Practices

  • Use workers for CPU-heavy tasks, not simple UI updates.
  • Always check for worker support before creating one.
  • Keep message payloads small when possible.
  • Use transferable objects for large binary data.
  • Terminate workers when they are no longer needed.
  • Handle error and messageerror events.
  • Do not access the DOM directly from inside a worker.
  • Design clear message types so communication stays predictable.

Performance Considerations

  • Creating workers has startup cost, so reuse them for repeated tasks when practical.
  • Avoid sending very large objects repeatedly between threads.
  • Prefer transferable buffers over deep cloning for big binary data.
  • Break long-running worker tasks into smaller chunks when possible.
  • Do not use workers for tiny calculations that finish instantly.
  • Test on lower-powered mobile devices, not just desktop hardware.

Common Web Worker Mistakes

  • Trying to access document or DOM elements inside a worker.
  • Assuming workers share variables directly with the main thread.
  • Forgetting to terminate workers after completing a task.
  • Not handling worker load failures or runtime errors.
  • Using workers for small tasks that do not benefit from threading.
  • Sending non-cloneable objects such as functions through postMessage().
  • Blocking the worker with infinite loops or unbounded work.

Web Workers vs Main Thread

Feature Web Workers Main Thread
DOM Access Not available. Full DOM and UI control.
Best For Heavy calculations and background processing. Rendering, events, and user interactions.
Communication Message passing with postMessage(). Direct function calls and DOM updates.
UI Impact Does not block the UI when used correctly. Long tasks can freeze the page.

Key Takeaways

  • Web Workers run JavaScript in background threads separate from the UI.
  • Use new Worker() to create a worker and postMessage() to communicate.
  • Workers cannot access the DOM, but they excel at data processing and heavy computation.
  • Feature detection, error handling, and worker termination are essential in production.
  • Transferable objects improve performance when moving large binary data between threads.

Pro Tip

Start with a simple worker that receives a message and returns a result. Once that works, move real heavy tasks such as parsing, filtering, or image processing into the worker and keep DOM updates on the main thread.