前言
最近在做微信小程序直播间秒杀抢券的功能,功能完成提测后,测试过来说这倒计时不对呀,在不同手机上倒计时有误差。听到反馈,第一反应就是又要掉头发了。于是,当即开始查找资料,定位问题,解决后,写此文章做个记录。
注意:用来计算倒计时的当前时间,不可以取用户客户端的时间(因为客户端时间用户可以随时调整,会造成时间不一致),应该是取后端返回的服务器的时间。
1、最初使用setInterval执行倒计时定时器,由于javascript是单线程的,同一时间只能执行一个js代码(同一时间其他异步事件执行会被阻塞 ) ,导致定时器事件每次执行都会有时间误差,甚至误差会越来越大。
最初代码如下:
Page({
/**
* 页面的初始数据
*/
data: {
timer: null, // 定时器
countdownEndTime: '', // 倒计时结束时间 即开始时间
countdownTime: '抢', // 倒计时显示
},
// 倒计时
countTime: function () {
var that = this;
let countDownNum = that.getCountdownTime(); //获取倒计时初始值
if (countDownNum <= 0) return;
var string = that.getCountdownString(countDownNum);
that.setData({
countdownTime: string
});
var interval = 1000
that.data.timer = setInterval(function() {
countDownNum -= interval;
var string = that.getCountdownString(countDownNum);
if (countDownNum <= 0) {
clearInterval(that.data.timer);
} else {
that.setData({
countdownTime: string
})
}
}, interval)
},
getCountdownString: function (time) {
var m = Math.floor(time / 1000 / 60 % 60);
var s = Math.floor(time / 1000 % 60);
if (m <= 0 && s<= 10) {
return s;
}
s = s < 10 ? "0" + s : s;
m = m < 10 ? "0" + m : m;
var string = `${m}:${s}`;
return string;
},
getCountdownTime: function () {
var that = this;
var time = that.data.countdownEndTime - that.data.dataTimestamp;
return time;
}
})
2、首先我们来看一个定时器执行时间测试:
var start = new Date().getTime(), count = 0
setInterval(function () {
count++
console.log(new Date().getTime() - (start + count * 1000) + 'ms')
}, 1000)
目测代码运行结果,定时器每秒执行一次,每次输出应该是0,但结果并非如此,实际输出如下:
结论:由于代码执行占用时间和其他事件阻塞原因,导致定时器事件执行延迟了几ms,但影响较小。
加下来加一段阻塞线程的代码,再次测试:
// 占用线程事件
setInterval(function () {
var n = 0
while (n++ < 1000000000);
}, 1000)
var start = new Date().getTime(), count = 0
setInterval(function () {
count++
console.log(new Date().getTime() - (start + count * 1000) + 'ms')
}, 1000)
执行结果输出如下:
结论:由于加了很占线程的阻塞事件,导致定时器事件每次执行延迟越来越严重。
由于实际项目中,执行计时器的同时,会有很多其他异步阻塞事件,会导致倒计时功能不精确。
3、解决方案
通过引入计数器,判断计时器延迟执行的时间来进行误差修正,尽量让误差缩小,不同浏览器不同时间段打开页面倒计时误差可控制在1s以内。
最终代码修改为setTimeout:
// 倒计时
countTime: function () {
var that = this;
let countDownNum = that.getCountdownTime(); //获取倒计时初始值
if (countDownNum <= 0) return;
var string = that.getCountdownString(countDownNum);
that.setData({
countdownTime: string
});
var interval = 1000, start = new Date().getTime(), count = 0;
that.data.timer = setTimeout(countDownStart, interval);
function countDownStart() {
var offset, nextTime; // offset是倒计时误差时间,nextTime是减去误差时间后下一次执行的时间
count++;
offset = new Date().getTime() - (start + count * interval);
nextTime = interval - offset;
if (nextTime < 0) { nextTime = 0; }
countDownNum -= interval;
var string = that.getCountdownString(countDownNum);
console.log("误差: " + offset + "ms, 下一次执行: " + nextTime + "ms后,离活动开始还有: " + countDownNum + "ms");
if (countDownNum <= 0) {
clearTimeout(that.data.timer);
} else {
that.setData({
countdownTime: string
})
that.data.timer = setTimeout(countDownStart, nextTime);
}
}
}
运行结果如下:
参考文章:https://www.jianshu.com/p/24895fdba736