一、需求:
项目的页面需要轮训抓取数据,而且要支持暂停抓取,继续抓取。还需要累计抓取的时间,以分钟为单位。
二、实现
一般情况下,前端多数是用setTimeout或者setInterval去每一秒对一个变量累计加1,然后除以60,就可以得到以分钟为单位的累计时间,就像这样:
let second = 0;
let minute = 0;
setInterval(() => {
second += 1;
minute = Math.floor(second / 60); // 分钟
}, 1000);
这种情况看起来好像没问题,但熟悉js的同学应该一眼就看出毛病了。因为js是单线程,每个回调函数都要经过事件队列才能到达主线程执行。
所以假如在事件队列里,有一个复杂耗时的事件在setInterval的回调函数之前,它需要花费超过1秒的时间才能完成的话,那么setInterval的回调函数将会等待超过1秒才能执行。
这样就会导致误差的出现。 也就是说 second = 2的时候,它可能实际上已经是4,5秒了。
显然,上面的代码误差随着时间的推移,误差会越来越大。
后面想了想,我们可不可以通过时间戳来做计算呢?
就是 我们开始的时候,记录下开始抓取的时间戳(startDate),然后使用setTimeout每一秒记录下当前的时间戳(currentDate)。然后用(当前的时间戳 减去 开始的时间戳)再除以 60 * 1000,再用Math.floor向下取整它们的结果,这样就计算了累计抓取的分钟数。代码如下
setTimeout(function loopTimer() {
let currentDate = new Date();
minute = Math.floor((currentDate - startDate) / 60 * 1000);
setTimeout(loopTimer, 1000);
}, 1000);
按道理来说,这样也是有时间误差的。如上面所说,loopTimer可能不是准确的1秒后执行,但我们不是用second += 1累计的方式去计算,而是用时间戳。所以只要setTimeout的定时间隔足够短, minute = Math.floor((currentDate - startDate) / 60 * 1000)计算得越频繁,minute就会越准确