React App with WebSocket implemented inside WebWorker

In this blog we will create a setup to get streaming data from backend and log it in the frontend. The ideal solution today to handle streaming data is through WebSocket. But initialising the WebSocket in the main thread would slow down the website and make it feel laggy since constantly it would be handling the server data that is pushed in through the WebSocket.

One way to do this efficiently is by initialising the WebSocket inside a WebWorker. To be brief, WebWorker does not execute in the main thread instead it will be executed in a separate thread in parallel to the main thread which handles the rendering and handling of user actions. To get a detailed understanding of these two Web API’s you can go through the following blogs.

WebSockets — https://blog.logrocket.com/websockets-tutorial-how-to-go-real-time-with-node-and-react-8e4693fbf843/

WebWorkers — https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API

We will be getting a streamable data from the following endpoint

wss://api-pub.bitfinex.com/ws/2

The code to achieve this is split across 4 files.

1. App.js
2. DedicatedWorkerClass.js
3. SocketClass.js
4. worker.js

We will go through each of these files and understand how it works. A pictorial representation is given below.

App.js

Before we continue just keep in mind that the messages conveyed between worker and main thread follows the following pattern in this example. You can use whatever format that suits your needs.

{
“action”: <string>,
“data”: <object>,
“status”: <string>,
“error”: <object>
}

With that out of the way let us look at each of the components of App.js file.

From the componentDidMount lifecycle method we trigger the createWorker function, which will create an instance of DedicatedWorkerClass and also sends a message to the worker stating to initialise the WebSocket. DedicatedWorkerClass is a wrapper around the actual worker API.

Inside createWorker we give the constructor of DedicatedWorkerClass the function workerOnMessageHandler.

This method is responsible for handling the messages sent from the worker and do appropriate actions based on the action types. Based on the state isWebSocketActiveInWorker we enable or disable two buttons appropriately which will be used to start/stop the web socket connection.

DedicatedWorkerClass.js

Inside the constructor of DedicatedWorkerClass we initialise the worker object with the syntax

new Worker(//path to worker JS filer)

The name of the worker file doesn’t necessarily have to be worker.js you can name it as per your use case. Here we also set the onerror and onmessage event handlers of the function to the instance functions of the same class.

This function will be used by App.js to send messages to the worker(Refer line 26 of App.js).

This function will be used to terminate the worker thread. Before terminating the thread we terminate the WebSocket connection to make sure there are no open connections.

The onError and onMessage functions intern call the functions given by the user as inputs to the constructor which in this case will be the workerOnMessageHandler function of App.js

worker.js

Inside the worker class the term self refers to the worker object. Refer doc. So self.onmessage will set the onmessage handler of worker object. This event handler will be triggered whenever the main thread sends any message using the postMessage API. Before we transfer the control to actionSwitcher we validate the request data to make sure it follows the data communication pattern we have defined. For now I have sent true by default for any input.

The actionSwitch handler will read the message sent from the main thread and takes necessary action. So when receiving the INIT action from main thread it initialises an instance of SocketClass which is a wrapper around the WebSocket API. We send the function orderTableDataCallback as one of the parameters for the SocketClass. This function will be used by the Socket to post the data it is retrieving. We are following this pattern wherein we are sending a function to the Socket is that the postMessage function will not be available outside the worker scope. So when you try to access postMessage from inside SocketClass it will be undefined. Hence we pass a function callback which will be used to send the message.

SocketClass.js

When we call the init function of SocketClass in line 40 of worker.js file the above function will be called wherein we are initialising the webSocket with the streaming data URL endpoint. We are also setting the onclose, onmessage, onopen and onerror event handlers of the WebSocket.

Once the WebSocket connection is opened the above method gets triggered. Here we send the above event through the WebSocket to the backend. Here we are saying that we want to subscribe to books data. This is very specific to the streaming endpoint we are using so you don’t have to worry about this. But most of the streaming data based on sockets works like this wherein we open the connection to the endpoint, then we subscribe to specific data that we are interested in hearing. We are also sending a data back to the main thread stating the connection is opened successfully.

Similarly after we have subscribed to the data we will start receiving information from the BE continuously. We send this info again to the main thread using the dataCallback function which is the orderTableDataCallback function in line 22 of worker.js which uses the postMessage API of WebWorker to send the data back to the main thread.

And that’s how you get streaming data through WebSocket and WebWorker without blocking the main thread and slowing down the application.
Hope this was useful to you 😊

Senior Software Engineer — Frontend @Freshworks.