chrome和其他浏览器之间“setInterval”的差异,为什么?

时间:2018-05-01 11:31:32

标签: javascript

CODE 1

console.log('start');

const interval = setInterval(() => {
    console.log('setInterval');
}, 0);

setTimeout(() => {
    console.log('setTimeout 1');
    Promise.resolve()
        .then(() => {
            console.log('promise 3');
        })
        .then(() => {
            console.log('promise 4');
        })
        .then(() => {
            setTimeout(() => {
                console.log('setTimeout 2');
                Promise.resolve()
                    .then(() => {
                        console.log('promise 5');
                    })
                    .then(() => {
                        console.log('promise 6');
                    })
                    .then(() => {
                        clearInterval(interval);
                    });
            }, 0);
        });
}, 0);

Promise.resolve()
    .then(() => {
        console.log('promise 1');
    })
    .then(() => {
        console.log('promise 2');
    });

此代码在 Windows Edge / Mac Safari / Opera 浏览器上的结果如下:

start
promise 1
promise 2
setInterval
setTimeout 1
promise 3
promise 4
setInterval
setTimeout 2
promise 5
promise 6

但是当在Windows或Mac上运行chrome时,结果有两种情况。

有时结果与上述相同。
有时结果输出两个setInterval如下:

start
promise 1
promise 2
setInterval
setTimeout 1
promise 3
promise 4
setInterval
setInterval
setTimeout 2
promise 5
promise 6

我对结果感到困惑。

代码 2

function expendTime(k){
    console.log((new Date()).getTime());
    while(k < 10000){
        for(var j = 2; j < k; j++){
            if(k%j == 0){
                break;
            }
            if(j == k-1){
                console.log(k)
            }
        }
        k++;
    }
    console.log((new Date()).getTime());
}

var t = setInterval(expendTime,15,3);
setTimeout(function() {
    clearInterval(t);
},30);
.as-console-wrapper {
  max-height: 100% !important;
}

在chrome和其他浏览器上运行时此代码的结果不同。

我认为其他浏览器的结果是正确的,因为它与我的常识一致。

1 个答案:

答案 0 :(得分:1)

chrome's* implementation of setInterval会自我纠正,因此其呼叫会以确切的间隔触发,消除任何漂移 * 和Edge的一个?

下面是一个示例代码,它实现了这样一个漂移校正setInterval方法,在chrome中运行,你可以看到它具有与内置函数相同的效果,而其他实现和递归setTimeout将继续增加漂移。

// drift-correcting setInterval
// returns an object which "_id" property is the inner timeout id, so it can be canceled by clearInterval
function driftCorrectingInterval(cb, delay) {
  var begin = performance.now(), // what time is it?
    result = {
      _id: setTimeout(inner, delay)
    },
    passed = true; // a flag to avoid try-catch the callback
  return result;

  function inner() {
    if (!passed) return; // 'cb' thrown
    passed = false; // set up the callback trap

    var now = performance.now(),
      drift = (now - begin) - delay;
    begin += delay; // start a new interval

    result._id = setTimeout(inner, delay - drift);
    // call it at the end so we can cancel inside the callback
    cb();
    passed = true; // didn't throw we can continue
  }
  
}


// snippet-only tests
function test(ms) {

  function setTimeoutLoop(cb, ms) {
    function loop() {
      cb();
      setTimeout(loop, ms);
    }
    setTimeout(loop, ms);
  }

  var now = performance.now(),
    built_in_prev = now,
    timeout_prev = now,
    sCI_prev = now,
    built_in_elem = document.querySelector('#test_' + ms + ' .delay.built_in'),
    timeout_elem = document.querySelector('#test_' + ms + ' .delay.timeout'),
    sCI_elem = document.querySelector('#test_' + ms + ' .delay.sCI');

  setInterval(() =>  {
    var now = performance.now(),
      delay = (now - built_in_prev) - ms;
    built_in_prev += ms;
    built_in_elem.textContent = Math.round(delay);
  }, ms);

  setTimeoutLoop(() => {
    var now = performance.now(),
      delay = (now - timeout_prev) - ms;
    timeout_prev += ms;
    timeout_elem.textContent = Math.round(delay);
  }, ms);

  driftCorrectingInterval(() =>  {
    var now = performance.now(),
      delay = (now - sCI_prev) - ms;
    sCI_prev += ms;
    sCI_elem.textContent = Math.round(delay);
  }, ms);

}

test(1000);
[id^='test'] {
  border: 1px solid;
  padding: 0 12px
}
<div id="test_1000">
  <p>built in setInterval drift: <span class="delay built_in">0</span>ms</p>
  <p>built in setTimeout loop drift: <span class="delay timeout">0</span>ms</p>
  <p>driftCorrectingInterval drift: <span class="delay sCI">0</span>ms</p>
</div>

请注意current specs读作Firefox的实现,以及您理解的内容,即递归的setTimeout,而不考虑漂移。

确实timer initialization steps要求当 repeat 标志设置为true时,应将相同的参数传递给下一个计时器初始化步骤方法。

open issue讨论了这个问题,可能导致规范的修订,以便鼓励UA尽可能应用这种漂移校正。