Search code examples
node.jssocket.ioreal-time

SocketIO limit requests on progress watching


I'm using SocketIO for a small app, where users would receive updates whenever a change occurs. However, i'd like to implement it to have real time progress visualization in tasks that are done in server side.

However, if the task progress changes too fastly, this would result in tons of event emissions and i think this could decrease the app performance. Is there a way to limit event emits to a maximum of N per second (Emitting only the last one, with the last percent of the progress) ?


Solution

  • Yes, that can be done. It requires you to hold events for a short time to see if there are more events of the same kind coming and thus combine all of them into one. I will code up an example in a few minutes here.

    Here's a general idea for how you could do this:

    function emitMessageLast(socket, msg, data) {
        const queueTime = 500;           // wait for up to 500ms of idle time before sending latest data
        const longestWaitTime = 2000;    // wait no more than 2 seconds if data is being continuously sent
    
        function stopTimer() {
            if (socket._timer) {
                clearTimeout(socket._timer);
                socket._timer = null;
            }
        }
    
        function sendNow() {
            socket._lastMsg = msg;
            socket._lastTime = Date.now();
            return socket.emit(msg, data);
        }
    
        // if this is the first time we're sending this message
        // or it's been awhile since we last sent data
        // just send the new data immediately
        if (socket._lastMsg !== msg || !socket._lastTime || Date.now() - socket._lastTime > longestWaitTime) {
            stopTimer();
            return sendNow();
        }
    
        // at this point, we know we're sending the same message as has recently been sent
        socket._lastMsg = msg;
        socket._lastData = data;
        stopTimer();
        // set a timer so that if no more data has arrived before the timer fires, 
        // we sent the last data we saved
        socket._timer = setTimeout(() => {
            socket._timer = null;
            sendNow();
        }, queueTime);
    }
    

    The general idea for this code is as follows:

    1. When you get called with a message to send and no message of the same type has been recently sent, then send this one immediately and record the time it was sent.
    2. When you get called with a message to send and it's been more than longestWaitTime since you last sent a message, then send this one immediately. This means if the server is continuously sending data, the server will wait for up to longestWaitTime before sending the latest value of the data.
    3. When the server is sending data sporadically, it will wait up to queueTime (waiting to see if there's more data coming) before sending the last piece of data. It is essentially buffering the last message until no more messages have been send in the last queueTime and then a timer will fire off that last message.
    4. I've configured the defaults here so that it will delay sending data to the client for up to 500ms (while waiting to see if the server is about to send more data so it can avoid sending all the intermediate values of the data) figuring that if the client updates its status every 500ms, that is plenty often. And, if the server is continuously sending updates, then the server will skip up to 2000ms of updates to send just the one last update. Again, you can set these numbers however you see appropriate.