倒计时(setInterval)的业务场景思考

886 阅读2分钟

前言: 在未接触存在倒计时需求的业务之前,笔者就已经存在疑问了。各种秒杀平台,倒计时板块是如何实现的?在阿里的实习过程中业务遇到了需要实现倒计时板块的业务需求,故此整理。

我的疑惑在哪里?

疑惑的关键点在于JS中的EventLoop机制,不了解这部分的新人小伙伴可以参考我之前写的文章:白话EventLoop

众所周知,任务队列是JS代码执行机制的重要组成部分。 以 setTimeout setInterval 为代表的宏任务是任务队列中的一等公民。
显而易见,倒计时功能应该由setInterval来实现。但根据EventLoop的机制,setInterval的回调函数是会在预期的时间被 插入任务队列的队尾,而非直接执行
按照这种逻辑,若排在前边的宏任务执行时间过长(哪怕是执行时间很短,但恰好排在了setInterval的回调函数之前),都会影响计时器的计时!!

验证猜想

首先,我们在控制台启动一个计时器

let counter = 10000;
setInterval(()=>{
    console.log(`counter: ${counter--}`)
},1000)

然后声明一个sleep函数,来作为宏任务,阻塞线程。

function sleep(delay) {
    var start = (new Date()).getTime();
    while((new Date()).getTime() - start < delay) {
        continue;
    }
}

此时我们的控制台应该还在持续不断地进行倒计时:10000,9999……
此时执行sleep(2000),我们可以看到,控制台中的倒计时停滞了两秒,而后继续进行倒数。

综上我们可以看到sleep(2000)的本质其实只是模拟了同步代码的执行,在实际的业务场景中的宏任务大概率不会阻塞线程如此之久。但是哪怕只阻塞了0.1秒,也会对倒计时造成影响阿!

除此之外,如UI渲染、网络请求的callback等其他宏任务也同样遵从这一机制,可以阻塞倒计时的进行。

业务场景中如何解决?

  1. 在非秒杀、抢购等时间精确度强需求的场景下,我们尽量做到简化页面的逻辑,防止各种操作影响计时器的精度。
  2. 在秒杀,抢购场景下,可以使用与后端协同的方式共同维护时间精度。具体方法如:
    • 类似发送心跳的方式,每隔一段时间与后端通信,通过轮询方式来维持时间精确。
    • 在各种用户操作对应的事件中绑定时间同步请求。如点击按钮……
  3. 若有更好的解决方案再补充。。

后话

询问师兄后得知,上述方法已经是应用于各种阿里大促的最佳实践了~
这也侧面体现了JS语言在某些方面的拉垮吧 哈哈哈哈哈哈