JavaScript:如何每个整点执行一次操作?

20

我想每小时执行一些JS代码,但我不能使用

setInterval("javascript function",60*60*1000);

因为我想每个整点都执行一次,比如在1:00、2:00、3:00等等。我正在考虑像这样的东西:

var d;
while(true) {
  d = new Date();
  if ((d.getMinutes() == '00') && (d.getSeconds() == '00')){
    // my code here
  }  
}

但是速度太慢,而且效果不好。

谢谢任何想法。


15
“太慢了”这个说法可能有些婉转,对吧?那样会让你的浏览器永远卡死! - bfavaretto
1
什么是上下文?看起来可能有一个更好的解决方案在服务器上使用 Cron 作业或其他东西...但不知道它是用来做什么的,很难说。 - Peter Ajtai
17个回答

34

我会先查看现在的时间,计算距离下一个整点还有多长时间,然后等待这段时间。所以:

function doSomething() {
    var d = new Date(),
        h = new Date(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours() + 1, 0, 0, 0),
        e = h - d;
    if (e > 100) { // some arbitrary time period
        window.setTimeout(doSomething, e);
    }
    // your code
}

检查e > 100只是为了确保您不会在5毫秒的时间内执行setTimeout并进入疯狂的循环。


11

你可以每分钟运行一个时间间隔,检查时间是否为:00,然后再运行实际的代码。


11

看看这个:

function to_be_executed(){
    ...
    ...
    ...
    makeInterval();
}

function makeInterval(){
    var d = new Date();
    var min = d.getMinutes();
    var sec = d.getSeconds();

    if((min == '00') && (sec == '00'))
        to_be_executed();
    else
        setTimeout(to_be_executed,(60*(60-min)+(60-sec))*1000);
}

在设置新的时间间隔之前,您需要记得清除旧的时间间隔...否则做得很好。 - Ethan Brown

6

高性能&简短回答

const runEveryFullHours = (callbackFn) => {
  const Hour = 60 * 60 * 1000;
  const currentDate = new Date();
  const firstCall =  Hour - (currentDate.getMinutes() * 60 + currentDate.getSeconds()) * 1000 - currentDate.getMilliseconds();
  setTimeout(() => {
    callbackFn();
    setInterval(callbackFn, Hour);
  }, firstCall);
};

使用方法:

runEveryFullHours(() => console.log('Run Every Full Hours.'));

6
ES6版本,适用于NodeJS和所有现代浏览器 - 根据浏览器或服务器时钟在毫秒级别执行: ---------- 更新 ---------- 功能
const doSomething = something => {
  setTimeout(() => {
    something()
    doSomething(something)
  }, 3600000 - new Date().getTime() % 3600000)
}

使用方法

// Do something at next full hour and repeat forever
doSomething(() => console.log('Full hour reached!'))

---------- 旧答案,更多控制 ----------

功能

const doSomething = (something) => {
  let running = true
  let nextHour = () => {
    return 3600000 - new Date().getTime() % 3600000
  }
  let nextCall = setTimeout(() => {
    something()
    doSomething(something)
  }, nextHour())
  return {
    next() { return running ? nextHour() : -1 },
    exec() { something() },
    stop() {
      clearTimeout(nextCall)
      running = false
    },
    start() {
      clearTimeout(nextCall)
      nextCall = setTimeout(() => {
        something()
        doSomething(something)
      }, nextHour())
      running = true
    }
  }
}

使用方法

// Do something at next full hour and repeat forever
doSomething(() => console.log('Full hour reached!'))

// Do something every full hour & stop it
let obj = doSomething(() => console.log('Will I ever execute? :/'))
obj.next() // Time to next execution in milliseconds
obj.next() / 1000 // Time to next execution in seconds
obj.next() / 1000 / 60 // Time to next execution in minutes
obj.stop() // Stop executing every full hour
obj.start() // Continue executing every hour
obj.exec() // Execute now

更高的要求/较低的兼容性,而且不包含在一个漂亮干净的可调用/命名函数中? - John

4
您可以使用以下函数,非常容易根据您的需要进行调整。
它计算下一个时间间隔的持续时间,然后在每次运行脚本后重新运行脚本 (除非您将第三个参数设置为true)
function runOnInterval(interval_in_ms, function_to_run, only_run_once = false){
    setTimeout(()=>{
        function_to_run();
        if (!only_run_once) runOnInterval(...arguments);
    }, interval_in_ms - ((Date.now() - (new Date().getTimezoneOffset() * 6e4)) % interval_in_ms));
}

示例用法:

// Every day at midnight:
runOnInterval(24 * 60 * 60 * 1000, my_function_to_run);
// Every hour on the hour:
runOnInterval(60 * 60 * 1000, my_function_to_run);
// Every minute on the minute:
runOnInterval(60000, my_function_to_run);
// Every 10 seconds on the 10 second mark:
runOnInterval(1e4, ()=>console.log('this will be run every 10 seconds on the 10 second mark'));

4
这是我会采取的方法,进一步扩展之前的答案:
function callEveryFullHour(my_function) {

    var now = new Date();
    var nextHour = new Date(now.getFullYear(), now.getMonth(), now.getDate(), now.getHours() + 1, 0, 0, 0);
    var difference = nextHour - now;

    return window.setTimeout(function(){

        // run it
        my_function();

        // schedule next run
        callEveryFullHour(my_function);

    }, difference);
}

这样,您就可以在整点时启动或停止任何功能。
用法:
let my_function = () => { // do something };
let timeout = callEveryHour(my_function);

// my_function will trigger every hour

window.clearTimeout(timeout);

// my_function will no longer trigger on the hour

3

我提出了对Steffan提出的方法的改进,这个方法对我来说效果最好。

功能:

let doSomething = function (interval, task) {
      let running = false
      let nextExec = () => {
        if (interval < 0) interval = 1000
        return interval - (new Date().getTime() % interval)
      }
      let taskwrapper = () => {
        //console.log("do more...")
        task()
      }
      let trigger = () => {
        if (nextCall) clearTimeout(nextCall)
        if (running) {
          nextCall = setTimeout(() => {
            taskwrapper()
            trigger()
          }, nextExec())
        }
      }
      let nextCall = null
      return {
        next() {
          return running ? nextExec() : -1
        },
        exec() {
          taskwrapper()
        },
        stop() {
          if (nextCall) clearTimeout(nextCall)
          running = false
        },
        start() {
          if (!running) {
            running = true
            if (nextCall) clearTimeout(nextCall)
            trigger()
          }
        }
      }
    }

/*instantiate an object doSomething(interval, task) */
let obj = doSomething(5000, () => console.log('You go..!'))

/*schedule You go...! every 5 seconds */
obj.start()

/*stop printing  */
//obj.stop()

/*execute task once */
obj.exec()


3

您可以通过每次清除并设置时间间隔来完成。只是第一次,时间间隔不是一个小时,而是一个小时减去当前的分钟和秒数:

var d = new Date();
var secondsPastHour = d.getMinutes()*60 + d.getSeconds();
var intervalId = setInterval( myFn, 60*60*1000 - secondsPastHour*1000 );

function myFn() {
    // do stuff
    // ...
    clearInterval( intervalId );
    intervalId = setInterval( myFn, 60*60*1000 );
}

这样做的唯一问题是最终它可能会开始漂移...解决方法就是在函数内部执行与启动时相同的操作:
var d = new Date();
var secondsPastHour = d.getMinutes()*60 + d.getSeconds();
var intervalId = setInterval( myFn, 60*60*1000 - secondsPastHour*1000 );

function myFn() {
    // do stuff
    // ...
    clearInterval( intervalId );
    var d = new Date();
    var secondsPastHour = d.getMinutes()*60 + d.getSeconds();
    intervalId = setInterval( myFn, 60*60*1000 - secondsPastHour*1000 );
}

这是一个每分钟更新一次的概念验证(我不想等待整整一个小时来测试我的代码!): http://jsfiddle.net/dRsua/

2

您需要每分钟(或每秒,具体取决于您想要的计时器精度)运行一次setInterval函数,并在分钟为零时执行您的代码(顺便说一下,getMinutes返回0到59之间的数字)。

 function myTimer() {
        var d = new Date()
        if (d.getMinutes() == 0) {
        console.log("full hour");
        }
}

 timer = setInterval(function(){myTimer()},60000)

如果您不想让间隔每秒/每分钟运行,而是在确定您处于整点后触发新的整点间隔并清除初始间隔即可。
var myHourlyTimer = null;

     function myTimer() {
            var d = new Date()
            if (d.getMinutes() == 0) {
            console.log("full hour");
myHourlyTimer = setInterval(function(){myHourlyTimerFunction()},3600000);
clearInterval(timer)
            }
    }

 timer = setInterval(function(){myTimer()},60000)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接