Web Workers: For non-blocking User Interface
Tags: javascript, webperf
Published: 2020-08-15 13:31:24

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](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Functions_and_classes_available_to_workers) default methods and properties of the `window` object.

## Examples

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

{% codesandbox user-interface-wo-web-worker-xs7pr %}

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
```javascript
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.
```javascript
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](https://dev-to-uploads.s3.amazonaws.com/i/yzfgkvoy3c6d34ifocah.gif)

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](https://codepen.io/NikhilKumaran/project/full/AOjQYm)

I tried doing web worker example in [codesandbox](https://codesandbox.io/s/with-web-worker-0s3q7), but for some reason, I couldn't import the worker file(`const worker = new Worker("src/worker.js")`). Also, I couldn't embed the [codepen](https://codepen.io/NikhilKumaran/project/editor/AOjQYm) example as `DEV` only allows embedding `pens` and not `projects`

That's it, folks, Thanks for reading this blog post. Hope it's been useful for you. Please do comment your questions and suggestions.

## References
- https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers
- https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API
- https://blog.teamtreehouse.com/using-web-workers-to-speed-up-your-javascript-applications