【问题标题】:The differences of "setInterval" between chrome and other browsers, Why?chrome和其他浏览器的“setInterval”的区别,为什么?
【发布时间】:2018-05-01 11:31:32
【问题描述】:

代码 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和其他浏览器上运行的结果是不同的。

我认为在其他浏览器上的结果是正确的,因为它符合我的常识。

【问题讨论】:

  • 欢迎来到 Stack Overflow!寻求帮助时,请花时间以一致、易读的方式格式化和缩进您的代码。 (在寻求帮助时也是个好主意。)这次我已经为你做了。
  • 您是否假设零间隔会使其同步?如果你是,情况并非如此。
  • Re "Code 2": “这段代码在 chrome 和其他浏览器上运行时的结果是不同的。” 您在每种情况下看到了什么?你认为什么是常识版本?
  • 我猜所有其他库都使用某种递归 setTimeout 来模拟 setInterval 而 Chrome 使用其他东西(如果这种行为真的可以重现的话)

标签: javascript


【解决方案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 在可能的情况下应用这种漂移校正。

【讨论】:

  • 我认为这个答案对我有好处,请不要删除。我正在阅读它。现在@dippas
  • 相关:他们都应该做什么:html.spec.whatwg.org/#timer-initialisation-steps
  • @T.J.Crowder 有一个open issue 可能会导致规格发生变化。还没有时间浏览所有 cmets,所以我不确定当前讨论的方向。但我发现我应该使用 drift 而不是 delay,明天将使用这些注释进行编辑。
猜你喜欢
  • 1970-01-01
  • 2012-08-28
  • 1970-01-01
  • 1970-01-01
  • 2020-08-06
  • 1970-01-01
  • 1970-01-01
  • 2014-01-01
  • 1970-01-01
相关资源
最近更新 更多