Nikhil Kumaran S

Web Workers: For non-blocking User Interface

Web Workers: For non-blocking User Interface

In this post, I'll explain how you can use web workers to perform CPU intensive tasks without blocking the User Interface.

What is a Web Worker?

A web worker is a JavaScript code that runs on a background thread, separate from the main execution thread of a web application.

The advantage of this is that CPU intensive tasks can be performed in a separate thread, allowing the main (usually the UI) thread to run without being blocked/slowed down.

Web workers run in another global context that is different from the current window. You can run whatever JS code you like inside the worker thread, with some exceptions. For example, you can't directly manipulate the DOM from inside a worker, or use some default methods and properties of the window object.

Examples

First, let's see a laborious task(calculating prime numbers) implemented without web workers.

Here we have an animation running smoothly at 60fps in the main thread. If you click on the CPU intensive task button, it prints the last prime number generated within a random range. This calculation is also done on the same main thread without using web workers. So when you click the button, the animation freezes for a second(depends on your system specs). The animation I used is just for our easy visualization. All the user interactions will also be blocked because of our laborious operation.

To avoid this we can move our CPU intensive task to the separate background thread using web workers. Since web workers cannot modify the DOM and print the result, we'll communicate with it by passing messages.

index.js file - Main thread

const iterations = 200;
const multiplier = 1000000000;

if (!window.Worker) {
  console.log("Worker not supported in your browser");
} else {
  const worker = new Worker("src/worker.js");

  worker.onmessage = function (message) {
    console.log("Message received from worker");
    document.querySelector(".result").innerText =
      message.data.primes[message.data.primes.length - 1];
  };

  function doPointlessComputationsInWorker() {
    worker.postMessage({
      multiplier: multiplier,
      iterations: iterations,
    });
  }
  document.querySelector("button").onclick = doPointlessComputationsInWorker;
}

Here in index.js file, we are posting the input values(iterations and multiplier) using worker.postMessage to our web worker and waiting for the result via worker.onmessage

worker.js - Background thread.

function calculatePrimes(iterations, multiplier) {
  var primes = [];
  for (var i = 0; i < iterations; i++) {
    var candidate = i * (multiplier * Math.random());
    var isPrime = true;
    for (var c = 2; c <= Math.sqrt(candidate); ++c) {
      if (candidate % c === 0) {
        // not prime
        isPrime = false;
        break;
      }
    }
    if (isPrime) {
      primes.push(candidate);
    }
  }
  return primes;
}

onmessage = function (e) {
  console.log("Worker: Message received from main script");
  var iterations = e.data.iterations;
  var multiplier = e.data.multiplier;
  var primes = calculatePrimes(iterations, multiplier);

  postMessage({
    command: "done",
    primes: primes,
  });
};

Here in worker.js file, we are receiving the input(sent from index.js) using onmessage and calculating the primes and sending the data back to the main thread using postMessage

Result non-blocking UI

Now if I click the button, the animation runs smoothly without any issues as our laborious task now runs separately on a background thread. You can check the result here

I tried doing web worker example in codesandbox, but for some reason, I couldn't import the worker file(const worker = new Worker("src/worker.js")). Also codepen doesn't have embed option for projects yet.

That's it, folks, Thanks for reading this blog post. Hope it's been useful for you.

References