55

This is a pretty simple question really. If I use setInterval(something, 1000), can I be completely sure that after, say, 31 days it will have triggered "something" exactly 60*60*24*31 times? Or is there any risk for so called drifting?

Deniz Dogan
  • 25,711
  • 35
  • 110
  • 162

3 Answers3

43

Short answer: No, you can't be sure. Yes, it can drift.

Long answer: John Resig on the Accuracy of JavaScript Time and How JavaScript Timers Work.

From the second article:

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.

Both articles (and anything on that site) is great reading, so have at it.

Paolo Bergantino
  • 480,997
  • 81
  • 517
  • 436
  • I don't understand. In that second article, John Resig says "...setInterval will attempt to execute a callback every 10ms regardless of when the last callback was executed." This would mean no drift for setInterval. – chowey Nov 19 '14 at 22:19
  • 2
    It will attempt, but if after 10 ms the execution is blocked by another long-running function, it will drift. Another (stupid) example: what if the function you are executing every 10 ms takes 11 ms to run ? when will the second function run? I believe after 20 ms (the 10ms start will skip) – musikele Aug 29 '16 at 07:43
  • Basically, A simpler way to put it would be a user event (clicking the mouse) can happen when a timer should end, which can interrupt that end event. It can take up that process's turn on the CPU when the timer should have gone off. – HumbleWebDev Nov 16 '16 at 01:46
  • @chowey—while Resig's statement may be correct for some implementations (e.g. Chrome, Opera), it's demonstrably wrong in others (e.g. Safari, Firefox). – RobG Sep 21 '18 at 06:19
27

Here's a benchmark you can run in Firefox:

var start = +new Date();
var count = 0;
setInterval(function () {
    console.log((new Date() - start) % 1000,
    ++count,
    Math.round((new Date() - start)/1000))
}, 1000);

First value should be as close to 0 or 1000 as possible (any other value shows how "off the spot" the timing of the trigger was.) Second value is number of times the code has been triggered, and third value is how many times the could should have been triggered. You'll note that if you hog down your CPU it can get quite off the spot, but it seems to correct itself. Try to run it for a longer period of time and see how it handles.

Blixt
  • 49,547
  • 13
  • 120
  • 153
  • 11
    Here's a live version of the above, slightly tweaked to show the output in the browser and more clearly: http://jsfiddle.net/hqmLg/1/ – Phrogz Nov 17 '11 at 20:37
  • It never corrects itself for me. If I do something like 100 per second, it does ~85, but if I put in a console log it does ~30 w/o ever speeding up. - http://jsfiddle.net/pajtai/qXkNY/ (using Phrogz demo) – Peter Ajtai Dec 30 '12 at 01:25
  • Well, the tests I did above were on Ubuntu FF on a ~10 year old 32 bit laptop. I tried it on a new 64 bit Windows machine and FF was much closer. IE and Chrome were within 2 tenths for the average. – Peter Ajtai Dec 30 '12 at 03:20
  • 6
    [`driftless`](https://github.com/dbkaplun/driftless) is a drop-in replacement for `setInterval` that mitigates drift. – dbkaplun May 28 '18 at 00:41
2

(Sorry about my bad english) I had same problem about counting down function, i writed a function countdown() and loop with setInterval but its drifting 1-3 milliseconds per loop. Then i write a function that controls is there any drifting and fixed it.

It controls with real minute and second only. Here it is. Its works fine to me, i hope it will help you too.

$.syncInterval(functionname,interval,controlinterval)

example:

countdown(){ some code };
$.syncInterval(countdown,1000,60);

it says run countdown function every 1000 milliseconds and check it every 60 seconds

here is the code:

$.syncInterval = function (func,interval,control) { 
        var 
        now=new Date();
        realMinute=now.getMinutes(),
        realSecond=now.getSeconds(),
        nowSecond=realSecond,
        nowMinute=realMinute,
        minuteError=0,
        countingVar=1,
        totalDiff=0;

        var loopthat = setInterval(function(){

        if (nowSecond==0) {
            nowMinute++;
            nowMinute=nowMinute%60;
        };
        if (countingVar==0){

            now=new Date();
            realSecond=now.getSeconds();
            realMinute=now.getMinutes();

            totalDiff=((realMinute*60)+(realSecond))-((nowMinute*60)+(nowSecond));
            if(totalDiff>0){
                for (i=1;i<=totalDiff;i++) {
                    func();
                    nowSecond++;
                    countingVar++;
                };
            } else if (totalDiff==0){
                func();
                nowSecond++;
                countingVar++;
            } else if (totalDiff<0) {

            };
        } else {
            func();
            nowSecond++;
            countingVar++;
        };
        countingVar=countingVar%control;
        nowSecond=nowSecond%60;
    },interval);
};
tunabt
  • 78
  • 6