Skip to content

Instantly share code, notes, and snippets.

@manast
Last active November 15, 2023 22:08
Show Gist options
  • Star 62 You must be signed in to star a gist
  • Fork 20 You must be signed in to fork a gist
  • Save manast/1185904 to your computer and use it in GitHub Desktop.
Save manast/1185904 to your computer and use it in GitHub Desktop.
Accurate Javascript setInterval replacement
function interval(duration, fn){
var _this = this
this.baseline = undefined
this.run = function(){
if(_this.baseline === undefined){
_this.baseline = new Date().getTime()
}
fn()
var end = new Date().getTime()
_this.baseline += duration
var nextTick = duration - (end - _this.baseline)
if(nextTick<0){
nextTick = 0
}
_this.timer = setTimeout(function(){
_this.run(end)
}, nextTick)
}
this.stop = function(){
clearTimeout(_this.timer)
}
}
@ArnaudRinquin
Copy link

right*

@tanepiper
Copy link

@manast - I've implemented a new version of this at https://gist.github.com/4215634

Rather than calculate the difference between an updated baseline, this instead takes a start time and current time, and calculates a pretty accurate tick value to compensate for drift - I've had this running for hours and keep accurate to within 1-5ms.

A bonus of this version if you use the default 1000ms (1s) tick value, it also gives you an accurate H/M/S timing, as shown in the timer example

@german-bortoli
Copy link

This has a problem, when the window lose the focus, the timers speed up after focus again.

@weednation12
Copy link

Good code, integrated it and works perfect!

@englishextra
Copy link

@manast
I would add

timer && timer.run();

Why? Consider the code below:

        var si = new interval(50, function () {
                if ("undefined" !== typeof hiddenPreloadImage && hiddenPreloadImage && 0 !== si) {
                    si.stop(),
                    si = 0;
                    setStyleOpacity(superbox, 1),
                    setImmediate(function () {
                        window.progressBar && (progressBar.finish(), progressBar.hide());
                    });
                }
            });
        si && si.run();

@jsp1987
Copy link

jsp1987 commented Aug 8, 2018

I never thought that JavaScript setInterval wasn't perfectly accurate. I applied interval.js in my website and it worked perfectly. Thank you!

@fraktalfabrik
Copy link

I never thought that JavaScript setInterval wasn't perfectly accurate. I applied interval.js in my website and it worked perfectly. Thank you!

try creating a metronome and you'll soon find out how inaccurate it is ;)
http://music.lighting/Metronome/

@morganruffner
Copy link

I am trying to implement this but it does not stop when this.interval.stop() is called, is there any reason why this could be? I am creating it with: that = this;
this.interval = new interval(that.state.timerint, function(){
that.noMetCount()
})

@manast
Copy link
Author

manast commented Aug 8, 2020

@morganruffner try the latest version instead. I just updated the code.

@morganruffner
Copy link

@manast Unfortunately I still haven't been able to get it to work, I see that it is going into the stop function but the function continues being called.

@dehaotu
Copy link

dehaotu commented Oct 24, 2020

I am trying to implement this but it does not stop when this.interval.stop() is called, is there any reason why this could be? I am creating it with: that = this;
this.interval = new interval(that.state.timerint, function(){
that.noMetCount()
})

function interval(duration, fn){
  var _this = this
  _this.baseline = undefined
  
  _this.run = function(){
    if(_this.baseline === undefined){
      _this.baseline = new Date().getTime()
    }
    fn()
    _this.end = new Date().getTime()
    _this.baseline += duration
 
    _this.nextTick = duration - (_this.end - _this.baseline)
    if(_this.nextTick<0){
      _this.nextTick = 0
    }

    if (_this.run !== undefined) {
      _this.timer = setTimeout(function(){
        _this.run()
      }, _this.nextTick)
    }
  }

  _this.stop = function(){
    clearTimeout(_this.timer);
    _this.run = undefined;
  }

This works for me. What I assume has happened was, you were trying to call this.interval.stop() inside fn(), but in this case _this.run() will keep executing and creating a new setTimeout and assigned to _this.timer. To counter this, I marked _this.run as undefined in _this.stop, and before the assignment for _this.timer in the _this.run, I check if _this.run is still defined.

I hope this works for other people as well... Please correct me if I'm wrong.

@ihewro
Copy link

ihewro commented Apr 17, 2021

In the first time,it wait double douration, there is the fixed version and with the args:

export function Interval(fn,duration,...args){
    const _this = this;
    this.baseline = undefined

    this.run = function(flag){
        if(_this.baseline === undefined){
            _this.baseline = new Date().getTime() - duration
        }
        if (flag){
            fn(...args);
        }
        const end = new Date().getTime();
        _this.baseline += duration

        let nextTick = duration - (end - _this.baseline);
        if(nextTick<0){
            nextTick = 0
        }

        console.log(nextTick);
        _this.timer = setTimeout(function(){
            _this.run(true)
        }, nextTick)
    }

    this.stop = function(){
        clearTimeout(_this.timer)
    }
}

@docjojo
Copy link

docjojo commented Nov 15, 2023

Great tool!
Might want to have a look at https://github.com/docjojo/Timers

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment