Multithreading with Web Worker in React

blessonabraham

Blesson Abraham

Posted on May 3, 2022

Multithreading with Web Worker in React

Javascript is a single-threaded language, No matter what async operations you use like promises, async, await, settimeout, etc.. it all ends up in the exact call stack now or later.

If you have ever operated on enterprise projects, you know the main challenges are optimization and performance. So we try to make the UI as lightweight as attainable. As I've mentioned above micro tasks (async, await..) and macro tasks (settimeout, setinterval..) both are not executed on separate threads, It's just deferred and executed later on the same UI thread.

Here in this writing, we will leverage the API that's available in browsers and NodeJs to achieve multithreading in Javascript apps.

So let's get started by setting up a React app with Typescript.

 npx create-react-app demo-app --template typescript
Enter fullscreen mode Exit fullscreen mode

What are web workers?

Web-workers are APIs provided by browser/nodeJS which will take your intensive tasks into a separate thread and will do the execution. So the communication between the main thread and the worker thread is done via postMessages(). Web Workers don’t have access to some very crucial JavaScript features:

  • The DOM (it’s not thread-safe).
  • The window object.
  • The document object.
  • The parent object.

Now let's create a file in the src folder where we keep all the intensive long-running code.

If you see below we are using postMessage() to communicate back to the main thread and // eslint-disable-next-line no-restricted-globals because self cannot be used in react without context objects.

intensiveTasks.ts

const workercode = () => {

  // eslint-disable-next-line no-restricted-globals
  self.onmessage = function (e) {
    const data = e.data
    // eslint-disable-next-line no-restricted-globals
    self.postMessage(data);
  }

  setTimeout(() => {
    // eslint-disable-next-line no-restricted-globals
    self.postMessage("Worker Thread: Hi");
  }, 2000);


};

let code = workercode.toString();
code = code.substring(code.indexOf("{") + 1, code.lastIndexOf("}"));

const blob = new Blob([code], { type: "application/javascript" });
const worker_script = URL.createObjectURL(blob);

export default worker_script;
Enter fullscreen mode Exit fullscreen mode

And here we use the same postMessage() to communicate with the worker thread and myWorker.onmessage is used to receive the message back from the worker thread.

App.tsx

import { useEffect, useRef, useState } from 'react';
import './App.css';
import IntensiveTask from './intensiveTask';

function App() {

  const [currentMessage, setCurrentMessage] = useState<string>('')
  const [allMessages, setAllMessages] = useState<string[]>([])

  const initiateWebWorker = () => {
    var myWorker = new Worker(IntensiveTask);

    // When you want to get messages from Worker Thread
    myWorker.onmessage = (message: any) => {
      setCurrentMessage(message.data)
    };

    // When you want to send messages to worker thread  
    myWorker.postMessage('Main Thread: Hello');
  }

  useEffect(() => initiateWebWorker(), [])

  useEffect(() => setAllMessages([...allMessages, currentMessage]), [currentMessage])

  return (
    <div className="App">
      <div className="App-header">
        <b>Web Worker Example</b>
        <p>{allMessages.map((data) => <div>{data}</div>)}</p>
      </div>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

That's it! Now you can kickstart our react app using npm start command.

GitHub: https://github.com/blessonabraham/webworkers-react
Codesandbox: https://codesandbox.io/s/webworkers-react-8gvljs

💖 💪 🙅 🚩
blessonabraham
Blesson Abraham

Posted on May 3, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related