你可能不知道的setInterval的坑

你可能不知道的setInterval的坑

以前印象中一直记得setInterval有一些坑,可是一直不是很清楚那些坑是什么。今天去摸索了下以后,决定来作个记录以避免本身忘记,也但愿让更多人了解到这个坑。javascript

坑的地方

  1. setInterval会无视代码的错误。就算遇到了错误,它仍是会一直循环下去,不会中止。这就致使了可能你代码里存在着一些问题(好比你的代码可能有个必定几率下会发生的错误,而你使用setinterval来循环调用它,因为setinterval不会由于报错中止,因此这个问题可能被隐藏),但是却很难发现。java

    let count = 1;
    setInterval(function () {
        count++;
        console.log(count);
        if (count % 3 === 0) throw new Error('setInterval报错');
    }, 1000)
  2. setInterval会无视任何状况下定时执行。而在有些场景下,咱们是不但愿如此的。ajax

    好比说,咱们要实现一个功能,每隔一段时间要向服务器发送请求来查看是否有新数据。此时,若当时用户的网络状态很糟糕,客户端收到请求响应的时间大于interval循环的时间。而setInterval会无视任何状况下定时执行,这就会致使了用户的客户端里充斥着ajax请求。
    此时正确的作法应该是改用setTimeout,当用户发出去的请求获得响应或者超时后,再使用setTimeout递归发送下一个请求。这样就不会有setInterval的坑了。编程

  3. setInterval不能确保每次调用都能执行。咱们能够先看一个代码浏览器

    const startDate = new Date();
    let endData;
    // 第一个调用会被略过
    setInterval(() => {
      console.log('start');
      console.log(startDate.getTime());
      console.log(endDate.getTime());
      console.log('end');
    }, 1000);
    while (startDate.getTime() + 2 * 1000 > (new Date()).getTime()) {
    }
    endDate = new Date();

    咱们能够看到,第一次执行的setInterval函数输出的startDate和endDate差距在2s以上。而咱们的setInterval写的是每间隔1s执行一次。所以,咱们能够看出,第一次的setInterval函数调用被略过了。服务器

    这说明了:若是说你的代码执行时间会比较久的话,就会致使setInterval中的一部分函数调用被略过。所以你的程序若是依赖于setInterval的精确执行的话,那么你就要当心这一点了。网络

    固然,其实setTimeout也有这个问题。浏览器的定时器都不是精确执行的。就算你调用setTimeout(fn, 0),它也不能确保立刻执行。函数

解决方案

其实解决方案也很简单,就是使用setTimeout,而后再setTimeout里递归调用。code

好比说第一个和第二个坑就能够这样写:递归

function fn () {
  setTimeout(() => {
    // 程序主逻辑代码
    // 循环递归调用
    fn();
  }, 1000);
}
fn();

但是使用setTimeout后,咱们又可能会遇到一个问题,就是计时器的下次触发时间是在当前的触发时间上开始计算的。这对于第二个坑这种状况是合理的,但是有时候咱们又但愿它能“匀速”地被触发。也就是说,但愿计时器的触发时间尽量在计时器注册时间+周期*delay附近。这个时候,咱们就能够用预期下次发生的时间减去当前的时间来获得一个精确的delayTime。

我写了一个简单的函数来实现这一点:一开始调用该函数的时候,会记录当前的计时器注册时间,以及一个用来统计计算器调用次数的变量。以后在每次调用newFn的时候,都会使用预期下次发生的时间减去当前的时间来获得一个精确的delayTime。这样至少能够保证在一些状况下,计时器能够稍微精确的执行。

function accurateTimers (fn, expectDelayTime) {
  let init = false;
  let registDate = new Date(); // 计时器注册时间
  let count = 0;  // 计时器调用次数
  function newFn() {
    let delayTime;
    count++;
    if (!init) {
      init = true;
      delayTime = expectDelayTime;
    } else {
      delayTime = expectDelayTime * count + registDate.getTime() - new Date().getTime();
    }
    console.log(delayTime);
    setTimeout(() => {
      fn();
      newFn();
    }, delayTime);
  }
  newFn();
}
accurateTimers(function () {
  let startDate = new Date();
  // 延迟500ms
  while (startDate.getTime() + 500 > (new Date()).getTime()) {
  }
}, 1000);

结论

以上,就是本次文章的内容。这篇文章只是作一个简单的记录,但愿能帮你们了解到setInterval的坑的地方,在实际编程中能够少走点弯路。若是以为有用的话,欢迎点个赞或者关注哦。谢谢。