DEV Community

Cover image for Web workers in ReactJs
Suman Kumar
Suman Kumar

Posted on

Web workers in ReactJs

Have you ever faced a situation in which you are executing a block of code and it is freezing the UI for a few seconds? If yes then you surely need a solution to handle that block of code in the background.

In React we have a simple way to do these types of CPU-intensive processes in the background with the help of web workers. First of all what a web worker is?

A web worker is a JavaScript feature that allows you to run scripts in the background, separate from the main thread of your web page. This background execution enables you to perform tasks concurrently without blocking the user interface (UI) or causing it to become unresponsive. Web workers are particularly useful for handling computationally intensive or time-consuming operations without impacting the user's experience.

The use cases for a web worker are

  • Complex Calculations: Web workers are ideal for performing complex mathematical calculations or data processing tasks without impacting the main thread.
  • Large Data Handling: When dealing with large datasets or files, web workers can help process and parse the data in the background.
  • Background Services: Web workers can be used to run background services such as periodic data synchronization or background notifications.

So, let's try to implement a web worker in a React application.

First, we create a react project, and then we use a service faker to create 25000 user records.

import { faker } from '@faker-js/faker';

export function fetchUsers() {
    const users = [];

    for (let i = 0; i < 25000; i++) {
        let id = i + 1;
        let name = faker.person.fullName();
        let email = faker.internet.email();
        let joinedOn = faker.date.recent();
        let commentCount = faker.number.int({ min: 0, max: 100 });
        let user = {
            id,
            name,
            email,
            joinedOn,
            commentCount
        };
        users.push(user);
    }
    return Promise.resolve(users);
}
Enter fullscreen mode Exit fullscreen mode

Now, our goal is to sort these records ascending and descending based on "commentCount" by using a web worker.

So, first, we will create two files "src/app.worker.js" and "src/WebWorker.js".

In the "app.worker.js" file we will export an arrow function in which we will add an event listener on the window object(self) to listen to the "message". This "message" will be triggered from the UI with "users" and "type"(ascending or descending" variables.

export default () => {
  self.addEventListener('message', e => { // eslint-disable-line no-restricted-globals
      if (!e) return;
      let { users, type } = e.data;

      if(type === "asc") {
        users = users.sort((a, b) => a.commentCount - b.commentCount);
      } else {
        users = users.sort((a, b) => b.commentCount - a.commentCount);
      }

      postMessage(users);
  })
}
Enter fullscreen mode Exit fullscreen mode

And we will sort it according to the "type" variable and return it by "postMesssage" method.

And in the "WebWorker.js" we will export a class.

export default class WebWorker {
    constructor(worker) {
        const code = worker.toString();
        const blob = new Blob(['('+code+')()']);
        return new Worker(URL.createObjectURL(blob));
    }
}
Enter fullscreen mode Exit fullscreen mode

It will simply change the worker we defined in "app.worker.js" to an object URL.

Now, let's connect this webworker to the UI.

First we will import both the "worker" and "WebWorker" from the respective files.

import worker from './app.worker.js';
import WebWorker from './WebWorker';
Enter fullscreen mode Exit fullscreen mode

Then, we will initialize the "WebWorker" with the argument "worker".

const webWorker = new WebWorker(worker);
Enter fullscreen mode Exit fullscreen mode

First, we will work on the ascending logic. So, we will send the "users" and "type" to the worker by the "postMessage" method on this web worker.

webWorker.postMessage({ users, type: "asc"});
Enter fullscreen mode Exit fullscreen mode

Then, we will listen to the "message" and get the sorted data from the web worker like this.

webWorker.addEventListener('message', (event) => {
            const sortedList = event.data;

            setUsers(sortedList);
        });
Enter fullscreen mode Exit fullscreen mode

Same logic we will add for the descending function as well.

webWorker.postMessage({ users, type: "desc"});
        webWorker.addEventListener('message', (event) => {
            const sortedList = event.data;

            setUsers(sortedList);
        });
Enter fullscreen mode Exit fullscreen mode

So, now our sorting is working fine. But don't forget to terminate the web worker at the time of component unmounting.

return () => {
            webWorker.terminate()
        }
Enter fullscreen mode Exit fullscreen mode

So, finally, our application will look something like this.

Working Example- https://6505555525bea36e699fd62c--monumental-cuchufli-bc3416.netlify.app/

Github- https://github.com/sumankalia/react_web_workers/tree/type

If you want to watch this tutorial on YouTube in Hindi. Here's the link

If you like the above article, please clap on the article.

Thanks for reading.

Top comments (6)

Collapse
 
andrzej_kowal profile image
Andrzej Kowal • Edited

Good article. Thank you. Probably more scalable solution will be to move all the work with worker into custom hook. Would be good to add some generics to it, TData and TError for example, so that we can always pass down the expected type for maximum of reusability. Also might be good idea to add some kind of isRunning or status and error useState calls inside the hook, and return it back from hook call, in order to show some loader (or error message) on the UI while worker is executing.

Collapse
 
raulbattistini profile image
raulbattistini

As far as I remember, the worker thread can't communicate with the main thread, so you can't load a table in the worker and transfer the data to the main thread in order for you to render that in some component. I maybe wrong and may you correct me if I'm mistaken as well

Collapse
 
andrzej_kowal profile image
Andrzej Kowal • Edited

You might be right, but I'll double-check when I will have some free time and motivation. :) I'll share the results here, perhaps tomorrow, including the implementation.

Thread Thread
 
raulbattistini profile image
raulbattistini

Please do! I`d like to see it as well

Thread Thread
 
andrzej_kowal profile image
Andrzej Kowal

@raulbattistini @sumankalia I finally got some time. Here is the implementation (CRA + TypeScript):
github.com/MinskLeo/example-react-....

This is pretty basic implementation, but everything just works. We are not blocking main thread.

Thread Thread
 
raulbattistini profile image
raulbattistini

Hoooly smokes, this is nice. It is significantly speeding stuff up to say the least. Really cool, I'll even try to implement this someday