Timeout

March 25, 2021

Background

Javascript is async in nature. An operation can be put to wait to give up execution time. During this yield time, other operations such as GUI updates, can be run.

Besides IO bounded operations, the simplest way to put an action into wait to be executed later is setTimeout. setTimeout(action, 1000) will run action after 1 second. And the program can run whatever during this 1 second wait time. It is like a breathe time for the program.

Issue

I recently wrote a page that displays logs from a Kubernetes cluster though WebSocket. The logs starts from 24 hours back and then follows in realtime. The code looks something like the following:

let logPanel = document.getElementById('log-panel')
ws.onmessage(event => {
    logPanel.innerHTML += event.data
})

logPanel is just a <pre id="log-panel"></pre>. This code is straight forward: Whenever a log is received from the WebSocket ws, it appends the log to the logPanel. So the order is like: receives a message, displays it, receives another message, displays it, on and on.

However, at the very beginning, there are a huge amount of logs from the server. The page receives a message, receives another message, receives another message, on and on. It has to update the page in between, which is very small amount of time. You can see the problem here, there is no time for the page to display the message. You will feel the page is stuck for a while, then suddenly display some logs, and stuck again. It is not smooth at all.

Solution

The problem is there are too many logs too fast. The page does not have time to process each of them at the same time update the display. We need to give it some breathe time. Time to use setTimeout

Here is a better way:


let logPanel = document.getElementById('log-panel')
let updateTimer
let buffer = ""
ws.onmessage(event => {
    clearTimeout(updateTimer)
    buffer += event.data
    updateTimer = setTimeout(() => {
        logPanel.innerHTML += buffer
        buffer = ""
    }, 1)  
})

In this solution, the log from the server is immediately saved to a buffer. Then a task is created by setTimeout to display the buffer, and is scheduled in 1 ms. If during this gap, new message comes, then buffer is updated and the task is canceled and new one is create for 1 ms after. Until the WebSocket gets a little bit time, the task will be active and all the logs will be displayed. This is something similar to switchMerge in Rxjs. This gives very responsive and smooth updates of the log even at the beginning.

Conclusion

Use setTimeout to give your page a breathe time so it is not overwhelmed by huge amount of events.

javascript