JavaScript How to fix setInterval issue when the clamped minimum is slower than the one second for inactive tab? [duplicate]

I need to create a simple but accurate timer.

This is my code:

var seconds = 0;
setInterval(function() {
timer.innerHTML = seconds++;
}, 1000);

After exactly 3600 seconds, it prints about 3500 seconds.

  • Why is it not accurate?

  • How can I create an accurate timer?

Answer:1

Most of the timers in the answers here will linger behind the expected time because they set the "expected" value to the ideal and only account for the delay that the browser introduced before that point. This is fine if you just need accurate intervals, but if you are timing relative to other events then you will (nearly) always have this delay.

To correct it, you can keep track of the drift history and use it to predict future drift. By adding a secondary adjustment with this preemptive correction, the variance in the drift centers around the target time. For example, if you're always getting a drift of 20 to 40ms, this adjustment would shift it to -10 to +10ms around the target time.

Building on Bergi's answer, I've used a rolling median for my prediction algorithm. Taking just 10 samples with this method makes a reasonable difference.

var interval = 200; // ms
var expected = Date.now() + interval;

var drift_history = [];
var drift_history_samples = 10;
var drift_correction = 0;

function calc_drift(arr){
  // Calculate drift correction.

  /*
  In this example I've used a simple median.
  You can use other methods, but it's important not to use an average. 
  If the user switches tabs and back, an average would put far too much
  weight on the outlier.
  */

  var values = arr.concat(); // copy array so it isn't mutated
  
  values.sort(function(a,b){
    return a-b;
  });
  if(values.length ===0) return 0;
  var half = Math.floor(values.length / 2);
  if (values.length % 2) return values[half];
  var median = (values[half - 1] + values[half]) / 2.0;
  
  return median;
}

setTimeout(step, interval);
function step() {
  var dt = Date.now() - expected; // the drift (positive for overshooting)
  if (dt > interval) {
    // something really bad happened. Maybe the browser (tab) was inactive?
    // possibly special handling to avoid futile "catch up" run
  }
  // do what is to be done
       
  // don't update the history for exceptionally large values
  if (dt <= interval) {
    // sample drift amount to history after removing current correction
    // (add to remove because the correction is applied by subtraction)
      drift_history.push(dt + drift_correction);

    // predict new drift correction
    drift_correction = calc_drift(drift_history);

    // cap and refresh samples
    if (drift_history.length >= drift_history_samples) {
      drift_history.shift();
    }    
  }
   
  expected += interval;
  // take into account drift with prediction
  setTimeout(step, Math.max(0, interval - dt - drift_correction));
}
Answer:2

I agree with Bergi on using Date, but his solution was a bit of overkill for my use. I simply wanted my animated clock (digital and analog SVGs) to update on the second and not overrun or under run creating obvious jumps in the clock updates. Here is the snippet of code I put in my clock update functions:

    var milliseconds = now.getMilliseconds();
    var newTimeout = 1000 - milliseconds;
    this.timeoutVariable = setTimeout((function(thisObj) { return function() { thisObj.update(); } })(this), newTimeout);

It simply calculates the delta time to the next even second, and sets the timeout to that delta. This syncs all of my clock objects to the second. Hope this is helpful.

Answer:3

This is an old question but figured I'd share some code I use sometimes:

function Timer(func, delay, repeat, runAtStart)
{
    this.func = func;
    this.delay = delay;
    this.repeat = repeat || 0;
    this.runAtStart = runAtStart;

    this.count = 0;
    this.startTime = performance.now();

    if (this.runAtStart)
        this.tick();
    else
    {
        var _this = this;
        this.timeout = window.setTimeout( function(){ _this.tick(); }, this.delay);
    }
}
Timer.prototype.tick = function()
{
    this.func();
    this.count++;

    if (this.repeat === -1 || (this.repeat > 0 && this.count < this.repeat) )
    {
        var adjustedDelay = Math.max( 1, this.startTime + ( (this.count+(this.runAtStart ? 2 : 1)) * this.delay ) - performance.now() );
        var _this = this;
        this.timeout = window.setTimeout( function(){ _this.tick(); }, adjustedDelay);
    }
}
Timer.prototype.stop = function()
{
    window.clearTimeout(this.timeout);
}

Example:

time = 0;
this.gameTimer = new Timer( function() { time++; }, 1000, -1);

Self-corrects the setTimeout, can run it X number of times (-1 for infinite), can start running instantaneously, and has a counter if you ever need to see how many times the func() has been run. Comes in handy.

Edit: Note, this doesn't do any input checking (like if delay and repeat are the correct type. And you'd probably want to add some kind of get/set function if you wanted to get the count or change the repeat value.

Answer:4

Hya, I have the below setup: App.Component.Ts contents carForm: FormGroup; constructor( private fb: FormBuilder ) { this.carForm= this.fb.group({ name: '', type: '', ...

Hya, I have the below setup: App.Component.Ts contents carForm: FormGroup; constructor( private fb: FormBuilder ) { this.carForm= this.fb.group({ name: '', type: '', ...

I'm using a generated library (LoopBack's Angular SDK) for my model's CRUD operations, and finding it difficult to unit test controllers and services that make use of them. Here's an example where I'...

I'm using a generated library (LoopBack's Angular SDK) for my model's CRUD operations, and finding it difficult to unit test controllers and services that make use of them. Here's an example where I'...

  1. unit test promise
  2. unit test promise reject
  3. unit test promise.all
  4. unit test promise resolve
  5. unit test promise angular
  6. unit test promise angular 2
  7. unit test promise jest
  8. unit test promise mocha
  9. unit test promise jasmine
  10. unit test promise chain
  11. unit test promise catch
  12. unit test promise then
  13. unit test promise javascript
  14. unit test promise node.js
  15. unit test promise java
  16. unit test promise sinon
  17. node js unit test promises
  18. unit test promise example
  19. unit test promise method
  20. angular unit test promise then

Does anyone know how to handle the connected, disconnected, reconnecting and etc on Laravel Echo? I'm using VueJS btw

Does anyone know how to handle the connected, disconnected, reconnecting and etc on Laravel Echo? I'm using VueJS btw

I've seen variation of this question on Stackoverflow that do not answer the question. The problem is that the background image is placed using % or center, left, top, etc. But what we actually want ...

I've seen variation of this question on Stackoverflow that do not answer the question. The problem is that the background image is placed using % or center, left, top, etc. But what we actually want ...