JS:具有动态间隔的计时器功能得到 "off" 一秒(不同步)?

JS: Timer function with dynamic interval getting "off" by one second(out of sync)?

我有一个带有动态间隔的简单渐进式定时器功能。

目标:

让这个函数在尽可能少的执行中每 XX:00:00 或 XX:30:00 运行。它从每秒 运行ning 开始,然后每 5 秒、10 秒、30 秒等,直到每 30 分钟达到用户的 :30 分钟标记或新的小时标记。
(我最终将在 AngularJS 驱动的日程应用程序中使用它来更新当前每半小时发生的事件。)


问题:

这个计时器工作得很好,但偶尔会关闭 1 秒并不断重置。

示例:

运行 如预期:

Tick 29: 22
Running every 1 second
Tick 29: 23
Running every 1 second
Tick 29: 24
Running every 1 second
Tick 29: 25
Running every 5 seconds
Tick 29: 30
Running every 30 seconds
Tick 30: 0
Running every 30 minutes
Tick 0: 0
Running every 30 minutes

Insanity(随机下车一秒)

Running every 1 second
Tick 30: 53
Running every 1 second
Tick 30: 55
Running every 5 seconds
Tick 31: 1
Running every 1 second
Tick 31: 3
Running every 1 second
Tick 31: 5
Running every 5 seconds
Tick 31: 11
Running every 1 second
Tick 31: 13
Running every 1 second
Tick 31: 15
Running every 5 seconds
Tick 31: 21
Running every 1 second
Tick 31: 22
Running every 1 second
Tick 31: 24
Running every 1 second
Tick 31: 26
Running every 1 second
Tick 31: 28
Running every 1 second
Tick 31: 30
Running every 30 seconds

我的问题:

为什么?为什么它似乎随机下车一秒钟,我该如何防止这种情况发生?有没有更好的方法来实现这一目标?我怀疑这可能与 Date().getSeconds()

有关

代码:

在 CodePen 上:http://codepen.io/StuffieStephie/pen/RKRVXe

/* Description: A dynamic timer function to run every XX:00:00 or XX:30:00 in as few executions as possible */
var one_sec   = 1000; // One second is 1,000 milliseconds
var one_min   = one_sec * 60; // Times 60 is a minute
var intervalms = one_sec;  // This is how often we'll run the function | Every 1s at first

//Some variables so we can output it to the screen instead of console
var messageHolder = document.getElementById("messageHolder");
var m             = one_sec; // MSG: What unit of time we're measuring in
var mString       = " second"; // MSG: String value of time measument

function tick() {
 clearInterval(interval); //Clear the timer so we can start with new dynamic value.
  var secs = new Date().getSeconds(); //get the second mark of the current time
    var mins = new Date().getMinutes(); //get the minute mark of the current time
     if (secs == "00"){ // If the value of secs is currently 0 (new minute XX:XX:00)
            intervalms = one_min;  // Set the interval every minute
            m          = one_min;
            mString = " minute"
         // Internal if: New minute AND
            // Minute mark is at XX:00:00 OR XX:30:00
               if (mins == "00" || mins == "30") {intervalms = 30 * one_min; mString =" minutes"; } //set every half-hour
              //Otherwise: if Minute mark is divisible by 10 (like 'XX:10:00', 'XX:20:00', etc)
          else if (mins % 10 == 0)  {intervalms = 10 * one_min; mString =" minutes";} //set every 10 minutes
            //Otherwise: if Minute mark is divisible by 5 (like 'XX:05:00', 'XX:10:00', etc)
          else if (mins % 5  == 0)  {intervalms =  5 * one_min; mString =" minutes";} //set every 5 minutes
         } // END if (new minute XX:XX:00)
    // Original if: Not a new minute     
     //Otherwise: if the value of secs is currently 30 (new minute XX:XX:30)
   else if (secs      == "30") {intervalms = 30 * one_sec; mString = " seconds";} //set every 30 seconds
     //Otherwise: if second mark is divisible by 10 (like 'XX:10:00', 'XX:20:00', etc)
   else if (secs % 10 ==   0 ) {intervalms = 10 * one_sec; mString = " seconds";} //set every 10 seconds
     //Otherwise: if second mark is divisible by 5 (like 'XX:05:00', 'XX:10:00', etc)
    else if (secs % 5  ==   0 ) {intervalms =  5 * one_sec; mString = " seconds";} //set every 5 seconds
  // if none of the above things are true `intervalms` is never reassigned and the function runs every second
  
    // EDIT: Sanity Check: Apparently it can get off so reset to 1s if needed
    else {intervalms = one_sec; mString = " second"; m = one_sec;}
    // Logging to console
    console.log('Tick ' + mins + ': ' + secs); //Output stuff to the console so we can see it
    console.log('Running every ' + (intervalms/m) + mString);
  
    // Appending content in the html
    msg = document.createElement("div");
    msg.innerHTML = 'Tick ' + mins + ': ' + secs +'<br>';
    msg.innerHTML += 'Running every ' + (intervalms/m) + mString;
    messageHolder.appendChild(msg);
  
    interval = setInterval(tick, intervalms);
}
var interval = setInterval(tick, intervalms);
<h1>A simple progrssive timer</h1>
<p>A dynamic timer function to run every XX:00:00 or XX:30:00 in as few executions as possible. </p>
<p>This timer can apparently get off at random?</p>
<div id="messageHolder"> </div>

不是答案,只是建议。你的 if else 东西真的很长。可以这样做:

function ticker(times,callback,i=0){
  setTimeout(function(){
    callback();
    ticker(times,callback,i+1);
  },times[i]);
 }

这样使用:

ticker([5,10,20,30,3000],function(){
  alert(new Date.getMinutes());
});

它将在5,10...秒后执行代码

或者如果你想执行半小时:

function allhalfhour(func){
   var time=30-(new Date().getMinutes()%30);//time missing to a multiple of a half hour (30 or 60);
   setTimeout(function(){
     func();
     setInterval(func,60*30);
   },60*time);
 }

这样使用:

allhalfour(function(){
 alert(new Date.getMinutes());
});

setTimeoutsetInterval 都会在超过几秒的时间内漂移很多,因此最好 运行 更频繁地使用计时器,然后仅在必要时调用该函数。

下面使用一般方程计算所需的延迟:

延迟到下一个运行 = 周期长度 - 自上一周期结束以来的时间 + 缓冲区

以下计算到下一个偶数周期的时间并适应可能具有 15 或 30 分钟分量的时区偏移(所有值均为毫秒):

var now = new Date();
var lengthOfPeriod = 30 * 60 * 1000; // or just 1.8e6
var timeSinceEndOfLast = (now.getMinutes() % 30) * 6e4 +
                          now.getSeconds() * 1000 +
                          now.getMilliseconds();

另一种方法是使用 Date.now 并通过本地时区偏移量调整值:

var now = new Date();
var lengthOfPeriod = 30 * 60 * 1000; // or just 1.8e6
var timeSinceEndOfLast = (Date.now() - now.getTimezoneOffset() * 6e4) % lengthOfPeriod;    

我还添加了一个小缓冲区以确保它 运行 正好在句点之后。这应该能够适应任何偶数时间段,例如分钟、5 分钟、30 分钟、2 小时,等等。

function showTime() {
  var clock = document.querySelectorAll('.clock');
  var now = new Date();
  clock[0].textContent = now.getHours();
  clock[1].textContent = ('0' + now.getMinutes()).slice(-2);
  clock[2].textContent = ('0' + now.getSeconds()).slice(-2);
}

/* Run provided function on each even multiple of period
** Function runs 90% of the way to the next period, or on period
** if delay is less than 1 second (1000 ms);
** @param {Function} fn - function to run
** @param {number} period - even period in ms (e.g. 60000 for one minute)
** @param {boolean} hold - don't run the first call (default is false)
*/
function doUpdate(fn, period, hold) {
  var buffer = 20;
  if (!hold) fn();
  hold = false;
  var now = new Date();
  var delayToNext = period - ((Date.now() - now.getTimezoneOffset() * 6e4) % period);
  if (delayToNext > 1000) {
    delayToNext *= .9;
    hold = true;
  }
  setTimeout(function(){doUpdate(fn, period, hold)}, delayToNext + buffer);
}

window.onload = function() {
  doUpdate(showTime, 60000); // run each minute
}
.clock {
  width: 2em;
  text-align: center;
}
<table>
  <tr><th>Hr<th>Min<th>Sec
  <tr>
    <td class="clock">
    <td class="clock">
    <td class="clock">
</table>