解决setTimeout/setInterval倒计时时间偏差问题

·  阅读 2142
解决setTimeout/setInterval倒计时时间偏差问题

这是我参与8月更文挑战的第5天,活动详情查看:8月更文挑战

产生原因

由于jsEventLoop执行机制,setTimeout/setInterval只是将事件插入了任务队列,必须等到当前代码(执行栈)执行完,主线程才会去执行它指定的回调函数。要是当前代码耗时很长,有可能要等很久,所以并没有办法保证,回调函数一定会在setTimeout()设定的延迟回调时间执行。所以此时 等待执行的实际时间 = 等待执行栈为空的时间 + 设定的延迟回调时间,此时时间偏差就产生了,即 时间偏差 = 等待执行栈为空的时间

可以这么去理解,setTimeout(fn,0)的含义是,指定某个任务在主线程最早可得空闲时间执行,也就是说,尽可能早的执行。它在任务队列的尾部添加一个事件,因此要等到同步任务和任务队列现有的事件都处理完,才会得到执行,而不是设定的回调时间0就会立刻去执行了。

解决办法

方法一

以后端返回服务器时间为准,通过定时向服务器发送请求,获取最新的时间时间偏差,以此来校准倒计时时间。但是这种方法消耗服务端资源较大,会存在并发问题;而且接口请求返回时间本身也存在时间偏差,增加了问题的不可控因素与复杂度

方法二

思路:
用递归的方法执行倒计时,在每次递归调用setTimeout回调的时候,计算出时间偏差,在下一次执行setTimeout时,把原设定的延迟回调时间减去时间偏差即可。

    const interval = 1000 // 设定倒计时规则为每秒倒计时
    let totalCount = 30000 // 设定总倒计时长为30s
    let count = 0 // 记录递归已执行次数,以倒计时时间间隔 interval=1s 为例,那么count就相当于如果没有时间偏差情况下的理想执行时间
    
    const startTime = new Date().getTime(); // 记录程序开始运行的时间
    let timeoutID = setTimeout(countDownFn, interval)
    
    // 倒计时回调函数
    function countDownFn() {
        count++ // count自增,记录理想执行时间
        // 获取当前时间减去刚开始记录的startTime再减去理想执行时间得到时间偏差:等待执行栈为空的时间
        const offset = new Date().getTime() - startTime - count * interval
        let nextTime = interval - offset // 根据时间偏差,计算下次倒计时设定的回调时间,从而达到纠正的目的
        if (nextTime < 0 ) {
            nextTime = 0
        }
        totalCount -= interval
        if (totalCount < 0) {
            clearTimeout(timeoutID)
        } else {
            timeoutID = setTimeout(countDownStart, nextTime)
        }
    }
复制代码

参考

阮一峰Eventloop

分类:
前端
收藏成功!
已添加到「」, 点击更改