Skip to content
Codeloom
JavaScript

Web Workers in JavaScript

Run JavaScript in background threads with Web Workers — dedicated workers, shared workers, message passing, and offloading heavy computation.

·3 min read · By Codeloom
Intermediate 9 min read

What you'll learn

  • How Web Workers run JavaScript in background threads
  • Sending and receiving messages between main thread and workers
  • Transferable objects for zero-copy data transfer
  • Practical patterns: CPU-heavy tasks, data processing

Prerequisites

  • JavaScript basics (async, events)
  • Understanding of the single-threaded event loop

JavaScript is single-threaded. Heavy computation blocks the UI — animations freeze, clicks are ignored. Web Workers run JavaScript in a separate thread, keeping the main thread responsive.

Creating a worker

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

worker.postMessage({ type: "compute", data: [1, 2, 3, 4, 5] });

worker.onmessage = (event) => {
  console.log("Result:", event.data);
};

worker.onerror = (error) => {
  console.error("Worker error:", error.message);
};
// worker.js
self.onmessage = (event) => {
  const { type, data } = event.data;

  if (type === "compute") {
    const result = data.reduce((sum, n) => sum + n, 0);
    self.postMessage(result);
  }
};

What workers CAN and CANNOT do

Can doCannot do
CPU-heavy computationAccess the DOM
Fetch API, WebSocketUse window object
IndexedDBAccess document
setTimeout/setIntervalUse UI APIs (alert, confirm)
Import scriptsShare memory directly (without SharedArrayBuffer)

Module workers

const worker = new Worker("worker.js", { type: "module" });
// worker.js — can use import
import { heavyComputation } from "./math.js";

self.onmessage = (event) => {
  const result = heavyComputation(event.data);
  self.postMessage(result);
};

Transferable objects

For large data, transfer ownership instead of copying.

// main.js
const buffer = new ArrayBuffer(1024 * 1024); // 1MB
worker.postMessage(buffer, [buffer]);
// buffer is now unusable in main thread — zero-copy transfer

Inline workers with Blob

No separate file needed:

const code = `
  self.onmessage = (e) => {
    const result = e.data.map(n => n * 2);
    self.postMessage(result);
  };
`;

const blob = new Blob([code], { type: "application/javascript" });
const worker = new Worker(URL.createObjectURL(blob));

worker.postMessage([1, 2, 3]);
worker.onmessage = (e) => console.log(e.data); // [2, 4, 6]

Promise-based wrapper

function runInWorker(workerFn, data) {
  return new Promise((resolve, reject) => {
    const code = `self.onmessage = (e) => {
      const fn = ${workerFn.toString()};
      const result = fn(e.data);
      self.postMessage(result);
    };`;

    const blob = new Blob([code], { type: "application/javascript" });
    const worker = new Worker(URL.createObjectURL(blob));

    worker.onmessage = (e) => {
      resolve(e.data);
      worker.terminate();
    };
    worker.onerror = reject;
    worker.postMessage(data);
  });
}

const result = await runInWorker(
  (numbers) => numbers.filter(n => isPrime(n)),
  Array.from({ length: 100000 }, (_, i) => i)
);

Worker pool

Reuse workers instead of creating new ones for each task.

class WorkerPool {
  constructor(workerScript, size) {
    this.workers = Array.from({ length: size }, () => new Worker(workerScript));
    this.queue = [];
    this.available = [...this.workers];
  }

  run(data) {
    return new Promise((resolve) => {
      const task = { data, resolve };
      const worker = this.available.pop();

      if (worker) {
        this.execute(worker, task);
      } else {
        this.queue.push(task);
      }
    });
  }

  execute(worker, task) {
    worker.onmessage = (e) => {
      task.resolve(e.data);
      const next = this.queue.shift();
      if (next) {
        this.execute(worker, next);
      } else {
        this.available.push(worker);
      }
    };
    worker.postMessage(task.data);
  }

  terminate() {
    this.workers.forEach((w) => w.terminate());
  }
}

Terminating workers

worker.terminate(); // immediate termination from main thread
self.close();       // graceful close from inside the worker

Summary

Web Workers move CPU-heavy JavaScript off the main thread. Use them for data processing, image manipulation, parsing, and any computation that would block the UI. Transfer large data with transferable objects for zero-copy performance. Use a worker pool to amortize creation cost across many tasks.