The differences of “setInterval” between chrome and other browsers, Why?

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');
    });

The result of this code on Windows Edge/Mac Safari/Opera browsers is as follow:

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

But when ran on chrome on Windows or Mac, the result have two cases.

Sometimes the result is same as above.
Sometimes the result outputs two setInterval as follow:

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

I'm confused about the result.

CODE 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;
}

The result of this code when ran on chrome and others browsers is different.

I consider the result on other browsers is correct, since it is coincides with my common sense.

Answers:

Answer

Yes chrome's* implementation of setInterval does self-correct itself so its calls do fire at exact intervals, removing any drift.
*and maybe Edge's one ?

Here is an example code which does implement such a drift-correcting setInterval method, ran in chrome, you can see it has the same effect than the builtin one, while other implementations and recursive setTimeout will keep growing the drift.

// 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>

Note that current specs read as Firefox's implementation, and as what you understood, i.e a recursive setTimeout, without any consideration for drift.

Indeed the timer initialization steps requires that when the repeat flag is set to true, the same arguments should be passed to the next timer initialization steps method.

But an open issue discusses this very problem and might lead to a revision of the specs so that UAs are encouraged to apply such drift-correction when possible.

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.