22

当分钟变化时,我怎样才能准确地运行一个函数?如果我在分钟变化时立即触发它,则使用 setInterval 可能会起作用。但我担心 setInterval 可能会在长时间运行的过程中被事件循环中断,并且不会与时钟保持同步。

分钟变化时如何准确运行函数?

4

9 回答 9

32

首先,您应该使用setInterval重复计时器,因为它(试图)保证定期执行,即任何潜在的延迟都不会像重复setTimeout调用那样叠加。这将每分钟执行一次你的函数:

var ONE_MINUTE = 60 * 1000;

function showTime() {
  console.log(new Date());
}

setInterval(showTime, ONE_MINUTE);

现在,我们需要做的是在正确的时间开始:

function repeatEvery(func, interval) {
    // Check current time and calculate the delay until next interval
    var now = new Date(),
        delay = interval - now % interval;

    function start() {
        // Execute function now...
        func();
        // ... and every interval
        setInterval(func, interval);
    }

    // Delay execution until it's an even interval
    setTimeout(start, delay);
}

repeatEvery(showTime, ONE_MINUTE);
于 2012-05-29T10:17:21.953 回答
2

这可能是一个想法。最大偏差应为 1 秒。如果您希望它更精确,请降低setTimeout1的毫秒数。

setTimeout(checkMinutes,1000);

function checkMinutes(){
  var now = new Date().getMinutes();
  if (now > checkMinutes.prevTime){
    // do something
    console.log('nextminute arrived');
  }
  checkMinutes.prevTime = now;
  setTimeout(checkChange,1000);
}

1但是,另请参阅这个问题,关于 javascript 中超时的准确性

于 2012-05-29T08:13:07.880 回答
1

我为您提出了一个可能的解决方案:

/* Usage:
 *
 * coolerInterval( func, interval, triggerOnceEvery);
 *
 *   - func : the function to trigger
 *   - interval : interval that will adjust itself overtime checking the clock time
 *   - triggerOnceEvery : trigger your function once after X adjustments (default to 1)
 */
var coolerInterval = function(func, interval, triggerOnceEvery) {

    var startTime = new Date().getTime(),
        nextTick = startTime,
        count = 0;

    triggerOnceEvery = triggerOnceEvery || 1;

    var internalInterval = function() {

        nextTick += interval;
        count++;

        if(count == triggerOnceEvery) {

            func();
            count = 0;
        }

        setTimeout(internalInterval, nextTick - new Date().getTime());
    };

    internalInterval();

};

以下是每分钟打印一次时间戳的示例用法,但时间漂移每秒钟调整一次

coolerInterval(function() {

    console.log( new Date().getTime() );

}, 1000, 60);

它并不完美,但应该足够可靠。考虑到用户可以在浏览器上切换选项卡,或者您的代码可能在页面上运行一些其他阻塞任务,因此浏览器解决方案永远不会完美,由您(和您的要求)决定它是否足够可靠或不。

于 2012-05-29T08:38:14.290 回答
1

您可以尝试尽可能准确,每 X 毫秒设置一次超时,并检查该分钟是否已经过去以及自上次调用该函数以来已经过去了多少时间,但仅此而已。

你不能 100% 确定你的函数会在 1 分钟后准确触发,因为那时可能有东西阻塞了事件循环。

如果它很重要,我建议为此使用 cronjob 或单独的 Node.js 进程(这样您就可以确保事件循环没有被阻塞)。

资源:

http://www.sitepoint.com/creating-accurate-timers-in-javascript/

于 2012-05-29T08:13:24.230 回答
1

在浏览器和 node.js 中测试

  • 休眠到分钟更改前 2 秒,然后等待更改
  • 您可以删除日志记录,因为它会在日志中变得非常混乱,否则

        function onMinute(cb,init) {
            
            if (typeof cb === 'function') {
            
                var start_time=new Date(),timeslice = start_time.toString(),timeslices = timeslice.split(":"),start_minute=timeslices[1],last_minute=start_minute;
                var seconds = 60 - Number(timeslices[2].substr(0,2));
                var timer_id;
                var spin = function (){
                                    console.log("awake:ready..set..");
                                    var spin_id = setInterval (function () {
                                        
                                        var time=new Date(),timeslice = time.toString(),timeslices = timeslice.split(":"),minute=timeslices[1];
                                        if (last_minute!==minute) {
                                            console.log("go!");
                                            clearInterval(spin_id);
                                            last_minute=minute;
                                            cb(timeslice.split(" ")[4],Number(minute),time,timeslice);   
                                            console.log("snoozing..");
                                            setTimeout(spin,58000);
                                        }
                                
                                    },100);
                                    
                                };
                
                setTimeout(spin,(seconds-2)*1000);
                
                if (init) {
                    cb(timeslice.split(" ")[4],Number(start_minute),start_time,timeslice,seconds);   
                }
            }
        
        }
        
        
        onMinute(function (timestr,minute,time,timetext,seconds) {
            if (seconds!==undefined) {
                console.log("started waiting for minute changes at",timestr,seconds,"seconds till first epoch");
            } else {
                console.log("it's",timestr,"and all is well");
            }
        },true);

于 2018-04-04T02:59:36.830 回答
0

我的第一个想法是使用Date 对象来获取当前时间。这将允许您通过一些简单的数学来设置每分钟的设置间隔。然后,由于您担心它会每 5-10 分钟或您认为合适的任何时间,您可以使用新的日期对象重新检查时间并相应地重新调整设置的时间间隔。

这只是我的第一个想法,虽然早上我可以放一些代码(这里就像凌晨 2 点)。

于 2012-05-29T08:07:38.350 回答
0

@Linus 提出的解决方案setInterval通常是正确的,但只要两分钟之间正好有 60 秒,它就会起作用。这个看似明显的假设在存在闰秒的情况下会失效,或者可能更频繁地,如果代码在暂停数秒的笔记本电脑上运行。

如果您需要处理这种情况,最好setTimeout每次手动调用调整间隔。像下面这样的东西应该可以完成这项工作:

function repeatEvery( func, interval ) {
    function repeater() {
        repeatEvery( func, interval);
        func();
    }
    var now = new Date();
    var delay = interval - now % interval;

    setTimeout(repeater, delay);
}
于 2019-02-23T19:25:27.740 回答
0

这是一个相当简单的解决方案......每次调用超时的时间间隔都会调整,因此它不会漂移,如果它提前触发,有一点 50 毫秒的安全性。

function onTheMinute(callback) {
    const remaining = 60000 - (Date.now() % 60000);
    setTimeout(() => {
        callback.call(null);
        onTheMinute(callback);
    }, remaining + (remaining < 50 ? 60000 : 0));
}
于 2018-12-17T12:21:09.137 回答
0

这是基于@Linus 的帖子和@Brad 的评论的另一个解决方案。唯一的区别是它不能通过递归调用父函数来工作,而只是setInterval()和的组合setTimeout()

function callEveryInterval(callback, callInterval){
    // Initiate the callback function to be called every
    // *callInterval* milliseconds.
    setInterval(interval => {
        // We don't know when exactly the program is going to starts 
        // running, initialize the setInterval() function and, from 
        // thereon, keep calling the callback function. So there's almost
        // surely going to be an offset between the host's system 
        // clock's minute change and the setInterval()'s ticks.
        // The *delay* variable defines the necessary delay for the
        // actual callback via setTimeout().
        let delay = interval - new Date()%interval
        setTimeout(() => callback(), delay)
    }, callInterval, callInterval)
}

小而有趣的事实:回调函数仅在 next 之后的一分钟更改时开始执行。

于 2019-02-08T20:05:24.977 回答