简体   繁体   中英

call function on the minute every minute

The code I wrote to call a function on the minute every minute, I think is flawed, as It's good for a while, but tends to lag behind by about 15 seconds for every hour since the page was loaded. To be honest I can't figure out what's causing the lagging, maybe it's the time it takes the functions to execute, small lapses all adding up and accumulating. Is there a way to auto-correct the lapses within the function as it's called. Or maybe someone knows a better method of achieving on the minute function calls. Any help or ideas much appreciated. Thanks.

var now = new Date();
var delay = 60 * 1000; // 1 min in msec
var start = delay - (now.getSeconds()) * 1000 + now.getMilliseconds();

setTimeout(function setTimer() {
  onTheMinFunc();
    setTimeout(setTimer, delay);
}, start);     

First of all, the DOM Timers API does not guarantee accuracy. I quote :

This API does not guarantee that timers will run exactly on schedule. Delays due to CPU load, other tasks, etc, are to be expected.

Second, you have a lag on each round caused by the time onTheMinFunc() is executed (you only set the timeout when it's done).

So, let's say onTheMinFunc takes half a second to execute - you get half a second delay at each minute and it accumulates - after only 10 minutes it'll lag quite a bit. (Note, functions should usually not take more than 15ms to execute anyway to avoid noticeable lag)

Try:

setInterval(onTheMinFunc, delay);

It still won't be very accurate. You can poll on much shorter intervals and keep track of a date variable - but again - no guarantees.

What you probably want is setInterval :

setInterval(onTheMinFunc, delay);  

As is, your code using setTimeout means that the time it takes to execute your onTheMinFunc is being added into your delay before the next one is started, so over time, this extra delay will add up.

Using setInterval will be more accurate, since the delay is between calls to execute the function, rather than starting the timer only after the function is finished.

Timers and javascript times aren't very accurate, and I would think the only way to make sure a function is executed every whole minute over time, is to check the seconds every second

setInterval(function() {
    if ( new Date().getSeconds() === 0 ) onTheMinFunc();
},1000);

FIDDLE

I think you want something closer to this:

function setNextMinute() {

    // figure out how much time remains before the end of the current minute
    var d = new Date().getTime()%60000;
    //set a timeout to occur when that expires.
    setTimeout(function () {
    // recalculate a new timeout so that your timer doesn't lag over time.
        doWhateverYouWantToHere();
        // note that calling doWhateverYouWantToHere() will 
        // not offset the next minute, since it is recalculated in setNextMinute()
        setNextMinute();
    },60000-d);
}
setNextMinute();

caveat: I did not thoroughly test this for timing. But it appeared to work for 1 sec intervals and 1 min intervals well enough.

This has the advantage of not recalculating every second, and also not just starting a 60 second timer from whatever the current time is.

Here is a slight modification to your code:

function everyMinute(fn) {
   arguments[1] && fn();
   var now = new Date();
   var delay = 60 * 1000 - (now.getSeconds()) * 1000 + now.getMilliseconds();
   setTimeout(function(){
     everyMinute(fn, true);
   }, start);
}
everyMinute(onTheMinFunc);

It recalculates the number of milliseconds to wait till the next minute every time so it is as accurate as possible to the top of the minute.

The current accepted answer may overkill

Executing if ( new Date().getSeconds() === 0 ) onTheMinFunc(); on each second (and forever) seems to not be a good idea.

I will not benchmark it against the following propositions, it's not necessary.

Clues

  1. Use whatever logic is necessary to calculate the start moment .
  2. On the start moment

    1. Use setInterval for remaning executions
    2. Execute the first call

      • Note setInterval is called ASAP to avoid that time lapses.

If you want that new Date().getSeconds() === 0 :

var id = setInterval(function() {
    if ( new Date().getSeconds() === 0 ) {
        setInterval(onTheMinFunc, delay);
        onTheMinFunc();
        clearInterval(id);
    }
},1000);

Alternatively, you could use your own logic:

var now = new Date();
var delay = 60 * 1000; // 1 min in msec
var start = delay - (now.getSeconds()) * 1000 + now.getMilliseconds();

setTimeout(function() {
  setInterval(onTheMinFunc, delay);
  onTheMinFunc();
}, start);

Please check both examples working on jsfiddle

The second (Example B) seems more accurate.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM