前言: 在未接触存在倒计时需求的业务之前,笔者就已经存在疑问了。各种秒杀平台,倒计时板块是如何实现的?在阿里的实习过程中业务遇到了需要实现倒计时板块的业务需求,故此整理。
我的疑惑在哪里?
疑惑的关键点在于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等其他宏任务也同样遵从这一机制,可以阻塞倒计时的进行。
业务场景中如何解决?
- 在非秒杀、抢购等时间精确度强需求的场景下,我们尽量做到简化页面的逻辑,防止各种操作影响计时器的精度。
- 在秒杀,抢购场景下,可以使用与后端协同的方式共同维护时间精度。具体方法如:
- 类似发送心跳的方式,每隔一段时间与后端通信,通过轮询方式来维持时间精确。
- 在各种用户操作对应的事件中绑定时间同步请求。如点击按钮……
- 若有更好的解决方案再补充。。
后话
询问师兄后得知,上述方法已经是应用于各种阿里大促的最佳实践了~
这也侧面体现了JS语言在某些方面的拉垮吧 哈哈哈哈哈哈