HTML Web Workers

Learn how to run JavaScript in background threads without blocking the UI

Web Workers allow you to run JavaScript code in background threads, enabling heavy computations without freezing the user interface. This makes web applications more responsive and improves performance.

What are Web Workers?

Web Workers are JavaScript scripts that run in the background, independently of the main execution thread. They don't have access to the DOM but can perform computationally intensive tasks without blocking the UI.

Key Benefits

  • Non-Blocking: UI remains responsive during heavy operations
  • Multi-Threading: True parallel execution in browsers
  • Performance: Better utilization of multi-core processors
  • Isolated Scope: Workers run in separate global context

Types of Web Workers

Type Description Use Case
Dedicated Workers Accessible by single script that created it Most common, general purpose
Shared Workers Accessible by multiple scripts across different windows Cross-tab communication

Checking for Web Worker Support

Always check if Web Workers are supported before using them:

Feature Detection

if (typeof(Worker) !== "undefined") {
  // Web Workers are supported
  console.log("Web Workers are available");
} else {
  // Web Workers are not supported
  console.log("Sorry, Web Workers are not supported");
}

Creating a Web Worker

To create a Web Worker, you need two files: the main script and the worker script.

Main Script (index.html)

<p>Count: <span id="result"></span></p>
<button onclick="startWorker()">Start Worker</button>
<button onclick="stopWorker()">Stop Worker</button>

<script>
var worker;

function startWorker() {
  if (typeof(Worker) !== "undefined") {
    if (typeof(worker) == "undefined") {
      // Create new worker
      worker = new Worker("worker.js");
    }

    // Listen for messages from worker
    worker.onmessage = function(event) {
      document.getElementById("result").innerHTML = event.data;
    };
  } else {
    document.getElementById("result").innerHTML =
      "Sorry, Web Workers are not supported";
  }
}

function stopWorker() {
  if (typeof(worker) !== "undefined") {
    worker.terminate();
    worker = undefined;
  }
}
</script>

Worker Script (worker.js)

var i = 0;

// Worker code runs in separate thread
function timedCount() {
  i = i + 1;
  // Send message to main script
  postMessage(i);
  setTimeout(timedCount, 500);
}

timedCount();

Worker Communication

Workers and the main script communicate through message passing:

Method/Event Used In Description
postMessage(data) Both Send message to the other side
onmessage Both Receive message from the other side
onerror Main script Handle worker errors
terminate() Main script Stop the worker immediately

Two-Way Communication

// Main script
var worker = new Worker("worker.js");

// Send data to worker
worker.postMessage({ cmd: 'calculate', data: [1, 2, 3, 4, 5] });

// Receive data from worker
worker.onmessage = function(e) {
  console.log("Result from worker:", e.data);
};

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

// ===== worker.js =====
onmessage = function(e) {
  var data = e.data;

  if (data.cmd === 'calculate') {
    var sum = data.data.reduce((a, b) => a + b, 0);
    // Send result back
    postMessage(sum);
  }
};

Web Worker Limitations

Workers run in a restricted environment with limited access to browser APIs:

❌ Not Available in Workers

  • DOM manipulation (no window, document, parent)
  • Direct access to the page (can't modify HTML)
  • Most browser APIs (alert, confirm, prompt)

✅ Available in Workers

  • navigator object
  • location object (read-only)
  • XMLHttpRequest / fetch()
  • setTimeout(), setInterval()
  • importScripts() to load other scripts
  • All JavaScript features (arrays, objects, functions, etc.)

Practical Examples

Heavy Computation Example

<button onclick="calculatePrimes()">Calculate Primes</button>
<p id="status"></p>
<p id="primes"></p>

<script>
function calculatePrimes() {
  document.getElementById("status").innerHTML = "Calculating...";

  var worker = new Worker("prime-worker.js");

  worker.postMessage({ max: 100000 });

  worker.onmessage = function(e) {
    document.getElementById("status").innerHTML = "Done!";
    document.getElementById("primes").innerHTML =
      "Found " + e.data.length + " prime numbers";
    worker.terminate();
  };
}
</script>

<!-- prime-worker.js -->
<script>
onmessage = function(e) {
  var max = e.data.max;
  var primes = [];

  // Calculate prime numbers (CPU intensive)
  for (var i = 2; i <= max; i++) {
    var isPrime = true;
    for (var j = 2; j < i; j++) {
      if (i % j === 0) {
        isPrime = false;
        break;
      }
    }
    if (isPrime) primes.push(i);
  }

  // Send result back
  postMessage(primes);
};
</script>

Data Processing Example

// Main script
var worker = new Worker("data-processor.js");

// Send large dataset
worker.postMessage({
  action: 'process',
  data: largeDataArray
});

worker.onmessage = function(e) {
  console.log("Processed data:", e.data.result);
  console.log("Processing time:", e.data.time + "ms");
};

// ===== data-processor.js =====
onmessage = function(e) {
  var startTime = Date.now();

  if (e.data.action === 'process') {
    var data = e.data.data;

    // Process data (filter, map, reduce, etc.)
    var result = data
      .filter(item => item.value > 100)
      .map(item => ({
        ...item,
        doubled: item.value * 2
      }))
      .reduce((sum, item) => sum + item.doubled, 0);

    var endTime = Date.now();

    postMessage({
      result: result,
      time: endTime - startTime
    });
  }
};

Image Processing Example

// Apply grayscale filter using worker
function applyGrayscale(imageData) {
  var worker = new Worker("image-worker.js");

  worker.postMessage({ imageData: imageData });

  worker.onmessage = function(e) {
    var processedData = e.data;
    // Draw processed image back to canvas
    ctx.putImageData(processedData, 0, 0);
    worker.terminate();
  };
}

// ===== image-worker.js =====
onmessage = function(e) {
  var imageData = e.data.imageData;
  var data = imageData.data;

  // Convert to grayscale
  for (var i = 0; i < data.length; i += 4) {
    var avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
    data[i] = avg;     // Red
    data[i + 1] = avg; // Green
    data[i + 2] = avg; // Blue
  }

  postMessage(imageData);
};

Importing Scripts in Workers

Workers can load external scripts using importScripts():

Loading External Libraries

// Inside worker.js
// Import one or more scripts
importScripts('utils.js');
importScripts('library1.js', 'library2.js');

// Now you can use functions from imported scripts
onmessage = function(e) {
  var result = utilityFunction(e.data); // From utils.js
  postMessage(result);
};

Web Workers Best Practices

Best Practices

  1. Use for CPU-Intensive Tasks: Complex calculations, data processing, image manipulation
  2. Minimize Message Passing: Large data transfers can be slow
  3. Terminate When Done: Call terminate() to free resources
  4. Handle Errors: Always implement onerror handler
  5. Transfer Objects: Use transferable objects for large data (ArrayBuffers)
  6. Pool Workers: Reuse workers instead of creating many
  7. Avoid DOM Access: Workers can't access the DOM
  8. Check Support: Always verify Web Workers are available
  9. Debug Carefully: Use browser dev tools to debug workers
  10. Keep Workers Simple: One task per worker for clarity

Transferable Objects (Fast Data Transfer)

// Create large ArrayBuffer
var buffer = new ArrayBuffer(1024 * 1024 * 32); // 32 MB

// Transfer ownership (fast, no copying)
worker.postMessage({ buffer: buffer }, [buffer]);

// After transfer, buffer is no longer accessible in main thread
console.log(buffer.byteLength); // 0

// In worker
onmessage = function(e) {
  var buffer = e.data.buffer;
  console.log(buffer.byteLength); // 33554432
  // Process buffer...
};

Test Your Knowledge