Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Run websocket in web worker or service worker - javascript

I have 9 websocket connections from different sites working to update the DOM with data. Currently I am connecting to all and listening to all websockets and updating the data with a function call.

The issue I'm facing is that there are many websocket connections and there are memory and CPU usage issues. How can I use either service workers and web workers to optimize so many websocket connections?

async function appendGatePublicTickersData(e) {
  if (e.event == "update" && e.result[0].contract == "BTC_USD") {
    if ('total_size' in e.result[0]) {
      $(".gate-btc-open-interest").html(commaNumber(e.result[0].total_size))
      if ('last' in e.result[0]) {
        $(".gate-btc-open-value").html(commaNumber(customFixedRounding((e.result[0].total_size / e.result[0].last), 4)))
      }
    }

    if ('volume_24h_usd' in e.result[0]) {
      $(".gate-btc-24-volume").html(commaNumber(e.result[0].volume_24h_usd))
    }

    if ('volume_24h_btc' in e.result[0]) {
      $(".gate-btc-24-turnover").html(commaNumber(e.result[0].volume_24h_btc))
    }

    if ('funding_rate' in e.result[0]) {
      var fundingRateBtcGate = customFixedRounding(e.result[0].funding_rate * 100, 4)
      $(".public-gate-btc-funding").html(fundingRateBtcGate)
    }

    if ('funding_rate_indicative' in e.result[0]) {
      var predictedRateBtcGate = customFixedRounding(e.result[0].funding_rate_indicative * 100, 4)
      $(".public-gate-btc-predicted").html(predictedRateBtcGate)
    }
  }
}

var pubGateWs = new WebSocket("wss://fx-ws.gateio.ws/v4/ws/btc");

pubGateWs.addEventListener("open", function() {
  pubGateWs.send(JSON.stringify({
    "time": 123456,
    "channel": "futures.tickers",
    "event": "subscribe",
    "payload": ["BTC_USD", "ETH_USD"]
  }))
});

pubGateWs.addEventListener("message", function(e) {
  e = JSON.parse(e.data)
  appendGatePublicTickersData(e)
});

pubGateWs.addEventListener("close", function() {});
like image 616
Pavneet Singh Avatar asked May 18 '20 08:05

Pavneet Singh


1 Answers

Since you are using Web Sockets it would be a good idea to use a SharedWorker to create a new thread for your Web Sockets. The difference between a normal WebWorker and a SharedWorker is that the web worker will create a new session in each tab or browser when loading the page, whereas the shared worker will use the same session in each tab. So all of your tabs or windows will have the same worker and same Web Socket connection to work with.

If the data is updated very frequently (more than 60 times per second) and the DOM has to be updated every time that happens, then use the requestAnimationFrame method to throttle the amount that the DOM is being updated. It will wait for the next repaint cycle before updating the DOM with new content, which is about 60 times per second, or 60FPS.

An implementation of this would like the example below:

Main thread.

// Create shared worker.
const webSocketWorker = new SharedWorker('web-sockets-worker.js');

/**
 * Sends a message to the worker and passes that to the Web Socket.
 * @param {any} message 
 */
const sendMessageToSocket = message => {
  webSocketWorker.port.postMessage({ 
    action: 'send', 
    value: message,
  });
};

// Event to listen for incoming data from the worker and update the DOM.
webSocketWorker.port.addEventListener('message', ({ data }) => {
  requestAnimationFrame(() => {
    appendGatePublicTickersData(data);
  });
});
  
// Initialize the port connection.
webSocketWorker.port.start();

// Remove the current worker port from the connected ports list.
// This way your connectedPorts list stays true to the actual connected ports, 
// as they array won't get automatically updated when a port is disconnected.
window.addEventListener('beforeunload', () => {
  webSocketWorker.port.postMessage({ 
    action: 'unload', 
    value: null,
  });

  webSocketWorker.port.close();
});

Shared Worker.

/**
 * Array to store all the connected ports in.
 */
const connectedPorts = [];

// Create socket instance.
const socket = new WebSocket("wss://fx-ws.gateio.ws/v4/ws/btc");

// Send initial package on open.
socket.addEventListener('open', () => {
  const data = JSON.stringify({
    "time": 123456,
    "channel": "futures.tickers",
    "event": "subscribe",
    "payload": ["BTC_USD", "ETH_USD"]
  });

  socket.send(data);
});

// Send data from socket to all open tabs.
socket.addEventListener('message', ({ data }) => {
  const payload = JSON.parse(data);
  connectedPorts.forEach(port => port.postMessage(payload));
});

/**
 * When a new thread is connected to the shared worker,
 * start listening for messages from the new thread.
 */
self.addEventListener('connect', ({ ports }) => {
  const port = ports[0];

  // Add this new port to the list of connected ports.
  connectedPorts.push(port);

  /**
   * Receive data from main thread and determine which
   * actions it should take based on the received data.
   */
  port.addEventListener('message', ({ data }) => {
    const { action, value } = data;

    // Send message to socket.
    if (action === 'send') {
      socket.send(JSON.stringify(value));

    // Remove port from connected ports list.
    } else if (action === 'unload') {
      const index = connectedPorts.indexOf(port);
      connectedPorts.splice(index, 1);
    }
  });

  // Start the port broadcasting.
  port.start();
});

Sidenote: your appendGatePublicTickersData does not use the await keyword, so it does not have to be an async function.


Now supported since Safari 16.
Browser support for Shared Web Workers

like image 159
Emiel Zuurbier Avatar answered Nov 08 '22 10:11

Emiel Zuurbier