Alex Wayne
Alex Wayne

Reputation: 187024

setInterval timing slowly drifts away from staying accurate

It seems that when I setInterval for 1000ms, it actually fires the function every 1001ms or so. This results in a slow temporal drift the longer its running.

var start;
var f = function() {
    if (!start) start = new Date().getTime();
    var diff = new Date().getTime() - start;
    var drift = diff % 1000;
    $('<li>').text(drift + "ms").appendTo('#results');
};

setInterval(f, 1000);

When run this shows the inaccuracy immediately.

See it for yourself: http://jsfiddle.net/zryNf/

So is there a more accurate way to keep time? or a way to make setInterval behave with more accuracy?

Upvotes: 37

Views: 20134

Answers (6)

Edhowler
Edhowler

Reputation: 835

The innacuracy in setInterval or setTimeout can be easily reproduced by changing tabs on Google Chrome. In order to treat those cases, you might want to considere making a condition for when the user is in another tab.

setTimeout(function() {
    if (!document.hasFocus()) {
        //... do something different, because more than 1 second might have passed
    }
}, 1000);

Upvotes: 0

A_A
A_A

Reputation: 1932

You can use this function to keep the calls close to the expected schedule. It uses setTimeout and calculates the next call time based on the elapsed time.

function setApproxInterval(callback, interval) {
  let running = true
  const startTime = Date.now()

  const loop = (nthRun) => {
    const targetTime = nthRun * interval + startTime
    const timeout = targetTime - Date.now()
    setTimeout(() => {
      if (running) {
        callback()
        loop(nthRun + 1)
      }
    }, timeout)
  }

  loop(1)
  return () => running = false
}

function clearApproxInterval(stopInterval) {
  stopInterval()
}

// Example usage
const testStart = Date.now()
const interval = setApproxInterval(() => console.log(`${Date.now() - testStart}ms`), 1000)
setTimeout(() => clearApproxInterval(interval), 10000)

Upvotes: 0

mVChr
mVChr

Reputation: 50177

Here's another autocorrecting interval. The interval is set to a shorter time period and then it waits until it's at least a second later to fire. It won't always fire exactly 1000ms later (seems to range from 0-6ms delay), but it autocorrects and won't drift.

EDIT: Updated to use recalling setTimeout instead of setInterval otherwise it may do something odd after 1000 or so iterations.

var start, tick = 0;
var f = function() {
    if (!start) start = new Date().getTime();
    var now = new Date().getTime();
    if (now < start + tick*1000) {
        setTimeout(f, 0);
    } else {
        tick++;
        var diff = now - start;
        var drift = diff % 1000;
        $('<li>').text(drift + "ms").appendTo('#results');
        setTimeout(f, 990);
    }
};

setTimeout(f, 990);

Run demo

Upvotes: 4

Alex Wayne
Alex Wayne

Reputation: 187024

I think I may have figured out a solution. I figured, if you can measure it you can compensate for it, right?

http://jsfiddle.net/zryNf/9/

var start;
var nextAt;

var f = function() {
    if (!start) {
        start = new Date().getTime();
        nextAt = start;
    }
    nextAt += 1000;

    var drift = (new Date().getTime() - start) % 1000;    
    $('<li>').text(drift + "ms").appendTo('#results');

    setTimeout(f, nextAt - new Date().getTime());
};

f();

result varies a bit but here's a recent run:

0ms
7ms
2ms
1ms
1ms
1ms
2ms
1ms
1ms
1ms

So if it gets called 1ms, 2ms or even 10ms later than it should the next call is scheduled to compensate for that. As long as inaccuracy is only per call, but the clock should never lose time, then this should work well.


And now I wrapped this up a global accurateInterval function which is a near drop in replacement for setInterval. https://gist.github.com/1d99b3cd81d610ac7351

Upvotes: 21

Phrogz
Phrogz

Reputation: 303225

I don't see a drift nearly as large as your script is reporting:
http://jsfiddle.net/hqmLg/1/

I'm leaving that script running. Right now (Chrome, Win 7) I see:

240 calls in 240.005s is 0.99979 calls/second

Indeed, I've seen the drift go up to .007s and then down to .003s. I think your measurement technique is flawed.

In Firefox I see it drift even more strongly (+/- 8ms either direction) and then compensate in the next run. Most of the time I'm seeing "1.000000 calls/second" in Firefox.

Upvotes: 1

Johnny Craig
Johnny Craig

Reputation: 5002

with a bit of googleing, you will see thatsetInterval and settimeout both will not execute the code at the exact specified time you tell it. with setInterval(f,1000); it will wait AT LEAST 1000MS before it executes, it will NOT wait exactly 1000MS. Other processes are also waiting for their turn to use the CPU, which causes delays. If you need an accurate timer that times at 1 second. I would use a shorter interval, like 50MS and compare it to the start time. I wouldnt go under 50MS though because browsers have a minimum interval

here are a few references:

"In order to understand how the timers work internally there's one important concept that needs to be explored: timer delay is not guaranteed. Since all JavaScript in a browser executes on a single thread asynchronous events (such as mouse clicks and timers) are only run when there's been an opening in the execution. This is best demonstrated with a diagram, like in the following:" taken from: http://css.dzone.com/news/how-javascript-timers-work

"Chrome and Chromium provide an interval that averages just over 41 milliseconds, enough of a difference for the second clock to be visibly slower in well under a minute. Safari comes in at just under 41ms, performing better than Chrome, but still not great. I took these readings under Windows XP, but Chrome actually performed worse under Windows 7 where the interval averaged around 46ms." taken from: http://www.goat1000.com/2011/03/23/how-accurate-is-window.setinterval.html

Upvotes: 10

Related Questions