DEV Community

Cover image for Create a Websocket client in Javascript using a web worker
JerDox
JerDox

Posted on

Create a Websocket client in Javascript using a web worker

This is a short tutorial on how to create a JavaScript WebSocket client that runs in a Web Worker.

The finished files can be downloaded at: github

Introduction:

The goal is to exchange data between a WebSocket server and the web client in real time. I decided to use a Web Worker to outsource the handling of the WebSocket data flow from the main thread,
to reducing the communication load.
I also implemented a reconnection logic, since the connection could not be establish or was closed unexpectedly

We will create two files:

  • index.html: contains the load of the web worker and processing datas from the WebSocket connection
  • websocketHandler.worker.js: The web worker file, handling of the WebSocket connection and datas

Short infos to Web workers

A web worker...

  • runs in a separate thread, independent from your web browser's main thread
  • cannot access or manipulate the DOM
  • communicates with the main thread using messages via "postMessage"
  • runs in a separate .js file

Creating the Websocket client

  • Let's start by creating a JavaScript file named websocketHandler.worker.js
  • At the top, we’ll add some variables that we’ll use later
let socket = null; // contains the socket object
let shouldReconnect = true; // a flag when we want to auto reconnect
let reconnectTimeout = null; // contains the reconnect timer
let wsServerUrl = '' // the URL to our WebSocket server
Enter fullscreen mode Exit fullscreen mode
  • Next we add a "onmessage" event function for handling incoming messages to the web worker.
onmessage = function (e) {   
    const data = e.data;
    handlingWebSocket(data);
};
Enter fullscreen mode Exit fullscreen mode

This will be triggered when a message sent to the web worker. The format for this messages will be: wsWorker.postMessage({ type: '<will be connect, send or disconnect>', payload: '<datas of the message>' });

  • Create also a function handlingWebSocket(data); for processing the WebSocket interface
function handlingWebSocket(data) {}
Enter fullscreen mode Exit fullscreen mode
  • Now in this function, we create a handling of the different incoming messages-types (connect, send, disconnect). For this create a switch-case for the different message-types:
 switch (data.type) {
    case 'connect':
        // Logic to open the connection to the websocket server
        break;

    case 'send':
        // sending a message to the web socket server
        break;

    case 'disconnect':
        // disconnet and do some clean up
        break;
    }
}
Enter fullscreen mode Exit fullscreen mode

Let's fill the different cases.

  • Case: connect - Connect to the Websocket server.

When message of type "connect" wsWorker.postMessage({ type: 'connect', wsServerUrl: 'ws://192.168.100.150:4040' }); is coming to the web worker,
we check if still a connection is open, clean the socket object for safty and then call a seperated connectToWebsocket function.

case 'connect':
    // save URL in local variable
    wsServerUrl = data.wsServerUrl

    // set auto reconnect, so when a connection could not be established it will try to reconnect again
    shouldReconnect = true

    // be sure that there is not still a WebSocket server object
    if (socket && socket.readyState !== WebSocket.CLOSED && socket.readyState !== WebSocket.CLOSING) {
        return;
    }

    // to be save the object is empty
    socket = null;

    // call the function to open the connection
    connectToWebSocket(wsServerUrl);
    break;
Enter fullscreen mode Exit fullscreen mode

Then we create and fill the connectToWebsocket function

function connectToWebSocket(wsServerUrl) {
    console.dir("Start connection to WebsocketServer...");

    // open the connection to the WebSocket server
    socket = new WebSocket(wsServerUrl);

    // event which triggered when the connection has been established
    socket.onopen = () => {
        // clear the timeout when the connection was established
        clearTimeout(reconnectTimeout);

        // send message to parent function that the connection was established
        postMessage({ type: 'connected' });
    };

    // event when a message comes from the WebSocket server
    socket.onmessage = (event) => {
        try {
            const msgContent = JSON.parse(event.data);
            postMessage({ type: 'data', payload: msgContent });
        } catch (err) {
            postMessage({ type: 'error', payload: "Error at parsing message datas" });
        }
    };

    // event will be triggered when an error with the WebSocket interfaces raised
    socket.onerror = (err) => {
        postMessage({ type: 'error', payload: err.message });
    };

    // event which triggered when the connection to the WebSocket server will be closed
    socket.onclose = () => {
        postMessage({ type: 'closed' });

        // clean up before try to reconnect
        disposeWebSocket()   

        // check if reconnection should be running
        if (shouldReconnect) {
            postMessage({ type: 'reconnect' });

            // set a timeout every 5sec for trying to reconnect
            reconnectTimeout = setTimeout(
            () => connectToWebSocket(wsServerUrl),
                5000);
        }     
    };
}
Enter fullscreen mode Exit fullscreen mode
  • We also have to create a dispose function "disposeWebSocket()" to do a clean up
function disposeWebSocket() {
    clearTimeout(reconnectTimeout);

    if (socket) {
        socket.onopen = null;
        socket.onmessage = null;
        socket.onerror = null;
        socket.onclose = null;

        socket = null;
    }
}
Enter fullscreen mode Exit fullscreen mode
  • Case: send - Next we fill the case for sending datas to the WebSocket sever. We check if the connection is still open before we send datas.
case 'send':
    // check if the connection still open before sending datas
    if (socket && socket.readyState === WebSocket.OPEN) {
        socket.send(JSON.stringify(data.payload));
    }
    break;
Enter fullscreen mode Exit fullscreen mode
  • Case: disconnect - Finally we create the case for disconnecting from the WebSocket server. We call this when we leave the page or the communication should be ending.
case 'disconnect':
    // prevent auto reconnect when the connection was active closed
    shouldReconnect = false

    if (socket) {
        // we call the close method and pass a status code.
        // code 1000 means normal closure
        socket.close(1000, "Client closing connection");
        console.dir("Sent CLOSE-connection request to server");
    }

    // call the dispose function to clean up
    disposeWebSocket();

    // set the command to end up the web worker semselfe
    self.close();

    break;
Enter fullscreen mode Exit fullscreen mode

As next we'll create the logic for using our WebSocket web worker

  • First create the index.html file with a default base structure like this:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>WebSocket Worker Demo</title>
</head>
<body>
    <h2>WebSocket Worker</h2>
    <div id="status">Connecting...</div>

    <!-- Button for sending datas to the websocket server -->
    <button onclick="sendDatasToWss()">send datas to WSS</button>

    <script>
        // here we palce the logig for the websocket interface
    </script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode
  • Inside the "srcipt" tag we start with create the web worker object
const wsWorker = new Worker('websocketHandler.worker.js');
Enter fullscreen mode Exit fullscreen mode
  • We use the object and send a postMessage of type: "connect" to call the web worker to connect to the WebSocket server like this:
wsWorker.postMessage({ type: 'connect', wsServerUrl: 'ws://192.168.100.150:4040' });
Enter fullscreen mode Exit fullscreen mode
  • Then we add a handling for messages from the web worker
wsWorker.onmessage = (event) => {
    const { type, payload } = event.data;
    const statusEl = document.getElementById('status');

    if (type === 'connected') {
        statusEl.textContent = "#### CONNECTION to WebSocketServer ESTABLISHED ####";
    } else if (type === 'data') {
        console.dir('Data incomming: ' + JSON.stringify(payload));
    } else if (type === 'error') {
        statusEl.textContent = "Error: " + JSON.stringify(payload);
    } else if (type === 'closed') {
        statusEl.textContent = "WebSocket is closed";
    }else if (type === 'reconnect'){
        statusEl.textContent = "Connection to WebSocketServer closed - Try to reconnect...";
    }
};
Enter fullscreen mode Exit fullscreen mode
  • We also add a function to test the sending of datas to the websocket server
function sendDatasToWss(){
    console.log("Sending datas to WSS");
    wsWorker.postMessage({ type: 'send', msgContent: 'SomeDatas you need in the WSS' });
}
Enter fullscreen mode Exit fullscreen mode
  • Do a clean up when the page will be leave
window.addEventListener('beforeunload', () => {
    wsWorker.postMessage({ type: 'disconnect' });

    // Give the worker time to clean up before terminate the web worker
    setTimeout(
        () => wsWorker.terminate(),
    10);
});
Enter fullscreen mode Exit fullscreen mode

When we now open the index.html file and the WebSocket connection should be established.

We see what's happening in the <div id="status">Connecting...</div> on the page.

Top comments (0)