Implementing countdown and considerations for JavaScript timers
1. JS Countdown#
The agenda plugin needs to add a pomodoro clock feature. I found usePomodoro on GitHub, which seems to have complete functionality and is easy to use with hooks. Although it doesn't have many stars, the feature is not complicated, and I can handle any issues that arise, so I chose it in the end.
However, after the feature was developed and deployed, I found that the timer was always inaccurate. Even though I set it to 25 minutes, an hour passed and the countdown didn't finish.
Later, I compared it with my phone and found that the discrepancy occurred when the software was in the background. I suddenly remembered encountering a similar situation before, where the timer could be inaccurate when the browser tab was inactive.
I checked the code in the repository and found the problem here:
// The original code uses setInterval to calculate the remaining time of the countdown
setInterval(tick, 1000)
When the tab is not active (hidden or the browser is in the background), the interval at which setInterval triggers is not necessarily 1000ms as set. This may be a mechanism designed by the browser for performance and power consumption considerations.
So how do we handle this?
Since JavaScript cannot guarantee that the code will execute every 1 second, we have to approach it from the outside. A common method is to use the system time as a reference:
function countDown(length) {
const start = new Date().valueOf()
const timer = setInterval(() => {
const now = new Date().valueOf()
const past = now - start
if (past <= 0) {
console.log('count down end')
clearInterval(timer)
}
}, 1000)
}
Knowing the start time and the duration of the countdown, we can calculate the end time and know the elapsed time in each timer callback. We delegate the timing task to the system, and JavaScript only polls the elapsed time. This way, we can avoid discrepancies.
I submitted this pull request.
There is also a problem with this method. If the user keeps the page inactive, when the countdown should end, setInterval may not execute. The tab needs to be activated for it to trigger again.
If we need to solve this problem, one solution is to wrap it as an app for background preservation, and another solution is to store the status information on the server and have the server send a message to JavaScript when the countdown ends.
2. Considerations for JavaScript Timers#
The basic usage is simple, but here we will only discuss a few points to pay attention to.
2.1 Nested Timers#
The callback of
setInterval
can include a call to another timer. To reduce the impact on performance, if the timers are nested more than 5 levels, the browser will enforce a minimum interval of 4ms for the timers.
2.2 Ensure the Execution Time is Shorter than the Timer Interval#
The timer callback is an asynchronous task, and the callback function will only be executed after the main thread of the timer-calling code has finished executing. If the preceding code takes longer than the timer interval to execute, the timer will not trigger at the correct time. Therefore, the browser cannot guarantee that the callback will be triggered at the exact time.
2.3 Minimum Delay for Inactive Tabs ≥ 1000ms#
To optimize the loading cost of inactive tabs (and reduce power consumption), the minimum delay for timers in inactive tabs is 1000ms.
2.4 Maximum Delay Value#
Browsers store delays as 32-bit signed integers, and when the delay exceeds 2147483647 milliseconds (approximately 24.8 days), it will overflow, and the timer will execute immediately.