Managing Long-Running Tasks In A React App With Web Workers

Managing Long-Running Tasks In A React App With Web Workers

Learn how to use the Web Worker API to manage time-consuming and UI-blocking tasks in a JavaScript app by building a sample web app that leverages Web Workers.

In this tutorial, we’re going to learn how to use the Web Worker API to manage time-consuming and UI-blocking tasks in a JavaScript app by building a sample web app that leverages Web Workers.

Response time is a big deal when it comes to web applications. Users demand instantaneous responses, no matter what your app may be doing. Whether it’s only displaying a person’s name or crunching numbers, web app users demand that your app responds to their command every single time. Sometimes that can be hard to achieve given the single-threaded nature of JavaScript. But in this article, we’ll learn how we can leverage the Web Worker API to deliver a better experience.

In writing this article, I made the following assumptions:

  1. To be able to follow along, you should have at least some familiarity with JavaScript and the document API;
  2. You should also have a working knowledge of React so that you can successfully start a new React project using Create React App.

If you need more insights into this topic, I’ve included a number of links in the “Further Resources” section to help you get up to speed.

First, let’s get started with Web Workers.

What Is A Web Worker?

To understand Web Workers and the problem they’re meant to solve, it is necessary to get a grasp of how JavaScript code is executed at runtime. During runtime, JavaScript code is executed sequentially and in a turn-by-turn manner. Once a piece of code ends, then the next one in line starts running, and so on. In technical terms, we say that JavaScript is single-threaded. This behavior implies that once some piece of code starts running, every code that comes after must wait for that code to finish execution. Thus, every line of code “blocks” the execution of everything else that comes after it. It is therefore desirable that every piece of code finish as quickly as possible. If some piece of code takes too long to finish our program would appear to have stopped working. On the browser, this manifests as a frozen, unresponsive page. In some extreme cases, the tab will freeze altogether.

Imagine driving on a single-lane. If any of the drivers ahead of you happen to stop moving for any reason, then, you have a traffic jam. With a program like Java, traffic could continue on other lanes. Thus Java is said to be multi-threaded. Web Workers are an attempt to bring multi-threaded behavior to JavaScript.

The screenshot below shows that the Web Worker API is supported by many browsers, so you should feel confident in using it.

Showing browser support chart for web workers

Web Workers browser support. (Large preview)

Web Workers run in background threads without interfering with the UI, and they communicate with the code that created them by way of event handlers.

An excellent definition of a Web Worker comes from MDN:

“A worker is an object created using a constructor (e.g. Worker()that runs a named JavaScript file — this file contains the code that will run in the worker thread; workers run in another global context that is different from the currentwindow. Thus, using the windowshortcut to get the current global scope (instead ofselfwithin aWorker will return an error.”

A worker is created using the Worker constructor.

const worker = new Worker('worker-file.js')

It is possible to run most code inside a web worker, with some exceptions. For example, you can’t manipulate the DOM from inside a worker. There is no access to the document API.

Workers and the thread that spawns them send messages to each other using the postMessage() method. Similarly, they respond to messages using the onmessage event handler. It’s important to get this difference. Sending messages is achieved using a method; receiving a message back requires an event handler. The message being received is contained in the data attribute of the event. We will see an example of this in the next section. But let me quickly mention that the sort of worker we’ve been discussing is called a “dedicated worker”. This means that the worker is only accessible to the script that called it. It is also possible to have a worker that is accessible from multiple scripts. These are called shared workers and are created using the SharedWorker constructor, as shown below.

const sWorker = new SharedWorker('shared-worker-file.js')

To learn more about Workers, please see this MDN article. The purpose of this article is to get you started with using Web workers. Let’s get to it by computing the nth Fibonacci number.

Computing The Nth Fibonacci Number

Note: For this and the next two sections, I’m using Live Server on VSCode to run the app. You can certainly use something else.

This is the section you’ve been waiting for. We’ll finally write some code to see Web Workers in action. Well, not so fast. We wouldn’t appreciate the job a Web Worker does unless we run into the sort of problems it solves. In this section, we’re going to see an example problem, and in the following section, we’ll see how a web worker helps us do better.

Imagine you were building a web app that allowed users to calculate the nth Fibonacci number. In case you’re new to the term ‘Fibonacci number’, you can read more about it here, but in summary, Fibonacci numbers are a sequence of numbers such that each number is the sum of the two preceding numbers.

Mathematically, it is expressed as:

Thus the first few numbers of the sequence are:

1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 ...

In some sources, the sequence starts at F<sub>0</sub> = 0, in which case the formula below holds for _n_ > 1:

In this article we’ll start at F1 = 1. One thing we can see right away from the formula is that the numbers follow a recursive pattern. The task at hand now is to write a recursive function to compute the nth Fibonacci number (FN).

After a few tries, I believe you can easily come up with the function below.

const fib = n => {
  if (n < 2) {
    return n // or 1
  } else {
    return fib(n - 1) + fib(n - 2)
  }
}

The function is simple. If n is less than 2, return n (or 1), otherwise, return the sum of the n-1 and n-2 FNs. With arrow functions and ternary operator, we can come up with a one-liner.

const fib = n => (n < 2 ? n : fib(n-1) + fib(n-2))

This function has a time complexity of 0(2<sup>n</sup>). This simply means that as the value of n increases, the time required to compute the sum increases exponentially. This makes for a really long-running task that could potentially interfere with our UI, for large values of n. Let’s see this in action.

Note: This is by no means the best way to solve this particular problem. My choice of using this method is for the purpose of this article.

To start, create a new folder and name it whatever you like. Now inside that folder create a src/ folder. Also, create an index.html file in the root folder. Inside the src/ folder, create a file named index.js.

Open up index.html and add the following HTML code.

<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <div class="heading-container">
    <h1>Computing the nth Fibonnaci number</h1>
  </div>
  <div class="body-container">
    <p id='error' class="error"></p>
    <div class="input-div">
      <input id='number-input' class="number-input" type='number' placeholder="Enter a number" />
      <button id='submit-btn' class="btn-submit">Calculate</button>
    </div>
    <div id='results-container' class="results"></div>
  </div>
  <script src="/src/index.js"></script>
</body>
</html>

This part is very simple. First, we have a heading. Then we have a container with an input and a button. A user would enter a number then click on “Calculate”. We also have a container to hold the result of the calculation. Lastly, we include the src/index.js file in a script tag.

You may delete the stylesheet link. But if you’re short on time, I have defined some CSS which you can use. Just create the styles.css file at the root folder and add the styles below:

body {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
  }

  .body-container,
  .heading-container {
    padding: 0 20px;
  }

  .heading-container {
    padding: 20px;
    color: white;
    background: #7a84dd;
  }

  .heading-container > h1 {
    margin: 0;
  }

  .body-container {
    width: 50%
  }

  .input-div {
    margin-top: 15px;
    margin-bottom: 15px;
    display: flex;
    align-items: center;
  }

  .results {
    width: 50vw;
  }

  .results>p {
    font-size: 24px;
  }

  .result-div {
    padding: 5px 10px;
    border-radius: 5px;
    margin: 10px 0;
    background-color: #e09bb7;
  }

  .result-div p {
    margin: 5px;
  }

  span.bold {
    font-weight: bold;
  }

  input {
    font-size: 25px;
  }

  p.error {
    color: red;
  }

  .number-input {
    padding: 7.5px 10px;
  }

  .btn-submit {
    padding: 10px;
    border-radius: 5px;
    border: none;
    background: #07f;
    font-size: 24px;
    color: white;
    cursor: pointer;
    margin: 0 10px;
  }

Now open up src/index.js let’s slowly develop it. Add the code below.

const fib = (n) => (n < 2 ? n : fib(n - 1) + fib(n - 2));

const ordinal_suffix = (num) => {
  // 1st, 2nd, 3rd, 4th, etc.
  const j = num % 10;
  const k = num % 100;
  switch (true) {
    case j === 1 && k !== 11:
      return num + "st";
    case j === 2 && k !== 12:
      return num + "nd";
    case j === 3 && k !== 13:
      return num + "rd";
    default:
      return num + "th";
  }
};
const textCont = (n, fibNum, time) => {
  const nth = ordinal_suffix(n);
  return `
  <p id='timer'>Time:  ${time} ms</p>
  <p> ${nth} fibonnaci number:  ${fibNum}</p>
  `;
};

Here we have three functions. The first one is the function we saw earlier for calculating the nth FN. The second function is just a utility function to attach an appropriate suffix to an integer number. The third function takes some arguments and outputs a markup which we will later insert in the DOM. The first argument is the number whose FN is being computed. The second argument is the computed FN. The last argument is the time it takes to perform the computation.

Still in src/index.js, add the below code just under the previous one.

const errPar = document.getElementById("error");
const btn = document.getElementById("submit-btn");
const input = document.getElementById("number-input");
const resultsContainer = document.getElementById("results-container");

btn.addEventListener("click", (e) => {
  errPar.textContent = '';
  const num = window.Number(input.value);

  if (num < 2) {
    errPar.textContent = "Please enter a number greater than 2";
    return;
  }

  const startTime = new Date().getTime();
  const sum = fib(num);
  const time = new Date().getTime() - startTime;

  const resultDiv = document.createElement("div");
  resultDiv.innerHTML = textCont(num, sum, time);
  resultDiv.className = "result-div";
  resultsContainer.appendChild(resultDiv);
});

First, we use the document API to get hold of DOM nodes in our HTML file. We get a reference to the paragraph where we’ll display error messages; the input; the calculate button and the container where we’ll show our results.

Next, we attach a “click” event handler to the button. When the button gets clicked, we take whatever is inside the input element and convert it to a number, if we get anything less than 2, we display an error message and return. If we get a number greater than 2, we continue. First, we record the current time. After that, we calculate the FN. When that finishes, we get a time difference that represents how long the computation took. In the remaining part of the code, we create a new div. We then set its inner HTML to be the output of the textCont() function we defined earlier. Finally, we add a class to it (for styling) and append it to the results container. The effect of this is that each computation will appear in a separate div below the previous one.

Showing computed Fibonacci numbers up to 43

Some Fibonacci numbers. (Large preview)

We can see that as the number increases, the computation time also increases (exponentially). For instance, from 30 to 35, we had the computation time jump from 13ms to 130ms. We can still consider those operations to be “fast”. At 40 we see a computation time of over 1 second. On my machine, this is where I start noticing the page become unresponsive. At this point, I can no longer interact with the page while the computation is on-going. I can’t focus on the input or do anything else.

Recall when we talked about JavaScript being single-threaded? Well, that thread has been “blocked” by this long-running computation, so everything else must “wait” for it to finish. It may start at a lower or higher value on your machine, but you’re bound to reach that point. Notice that it took almost 10s to compute that of 44. If there were other things to do on your web app, well, the user has to wait for Fib(44) to finish before they can continue. But if you deployed a web worker to handle that calculation, your users could carry on with something else while that runs.

Let’s now see how web workers help us overcome this problem.

react javascript api web-development programming

Bootstrap 5 Complete Course with Examples

Bootstrap 5 Tutorial - Bootstrap 5 Crash Course for Beginners

Nest.JS Tutorial for Beginners

Hello Vue 3: A First Look at Vue 3 and the Composition API

Building a simple Applications with Vue 3

Deno Crash Course: Explore Deno and Create a full REST API with Deno

How to Build a Real-time Chat App with Deno and WebSockets

Convert HTML to Markdown Online

HTML entity encoder decoder Online

How native is React Native? | React Native vs Native App Development

Article covers: How native is react native?, React Native vs (Ionic, Cordova), Similarities and difference between React Native and Native App Development.

The JavaScript Web History API With Practical Examples

Understanding The Web History API in JavaScript. API stands for Application Programming Interface. A Web API is an application programming interface for the web. The web history API is supported by all the browsers. We can build our own Web API using different technologies such as Java or Node and etc.

A Simple Guide to API Development Tools

APIs can be as simple as 1 endpoint for use by 100s of users or as complex as the AWS APIs with 1000s of endpoints and 100s of thousands of users. Building them can mean spending a couple of hours using a low-code platform or months of work using a multitude of tools. Hosting them can be as simple as using one platform that does everything we need or as complex as setting up and managing ingress control, security, caching, failover, metrics, scaling.

How To Write Better Code As A Web Developer - React

Look at three different React code examples from a beginner, intermediate, and advanced web developer. How senior developers think. How to use React state properly. How to use React useEffect properly. What to think about when programming. The differences between senior and junior developers

How to Become A React JavaScript Developer 🚀

Today Qazi & Sonny will be showing you How To Become a React JavaScript Developer 🚀👨‍💻