In daily requirements, we often need to write the countdown function. But sometimes we find that the speed is different. What's going on?
Let's think about it with questions. Should we use setInterval or setTimeout to write the countdown function?
- Suppose we use setInterval, we might write this
let interval = 1000 let countdown = () => { // do someting... } countdown() setInterval(countdown, interval)
We may think that its execution order is like this
Now I often make its execution greater than its interval. Let's take another look
We found that after the callback is executed, we do not wait a second to continue the execution, but start the execution immediately after the callback is executed
This is because setInterval will check whether a callback is being executed after the interval time arrives. If so, it will wait for the next cycle. No matter whether the callback is executed for 800 milliseconds or 2000 milliseconds, as long as the interval time expires, it will try to insert it into the event queue
Therefore, we need to write the countdown. It's best not to put too much logic in the callback. The callback execution time cannot be greater than interval, otherwise there will be no interval between the two executions
2. Suppose we use setTimeout, we might write this
let interval = 1000 countdown(interval) const countdown = (interval) => { let timer = setTimeout(() => { clearTimeout(timer) // do someting... countdown(interval) }, interval) }
Using setTimeout, we can ensure that there is an accurate interval for logical processing, and the above situation will not occur, but we will find another problem
Through the experiment, we can see that the interval between each printing will be longer and longer. In fact, in addition to the execution time of js, there is another error. Let's take a look at the official description
In addition, mdn also recommends how to implement a 0 millisecond delay timer, which I also put here
David Baron's weblog: setTimeout with a shorter delay!
So how to avoid this error? We can solve this problem through calculation, and attach the complete code
/** * Countdown tool * @param {object} option - Configuration item * @param {number} option.timeStemp - Timestamp, MS * @param {number} [option.interval=1000] - Interval, 1000 ms by default * @param {Function} [option.callback] - Callback * @returns {Function} cancel Stop timer * Note: whether you use this tool or write it yourself, the overall execution time of the callback should not exceed interval */ const countdownTool = function ({ timeStemp, interval: originalInterval = 1000, callback = (resetTime) => void 0, }) { if (!timeStemp || timeStemp <= 0 || timeStemp < originalInterval) return callback(0) let stop = false const cancel = () => { stop = true } let curIdx = 1 let interval = originalInterval let ct = Date.now() countdown(interval) function countdown (interval) { if (stop) return let timer = setTimeout(function () { clearTimeout(timer) let resetTime = timeStemp - originalInterval * curIdx if (resetTime < 0) resetTime = 0 callback(resetTime) if (!resetTime) return curIdx++ let ct2 = Date.now() let deviation = ct2 - interval - ct if (deviation >= originalInterval || deviation <= 0) deviation = 5 // Prevent malicious changes to local time ct = Date.now() countdown(originalInterval - deviation - (ct - ct2)) }, interval) } return cancel }
Under console execution
It can be seen that the time interval obtained in each callback is about 1000 milliseconds, and there will be no increasing time.
There are two other points to note:
1. Due to the js engine's optimization strategy for load loss, there will be more errors when the page is displayed and hidden. We can re execute it when it is displayed
- Timestamp is best taken from the server
Reference article: window.setTimeout - Web API interface reference | MDN
https://segmentfault.com/a/1190000040397253
David Baron's weblog: setTimeout with a shorter delay