Why is there always an error in the countdown you write

Posted by beesgirl713 on Sun, 02 Jan 2022 12:57:41 +0100

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?

  1. 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

  1. 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

Topics: Javascript Front-end ECMAScript