源起:

之前在因为远程办公在家远程办公的时候,自己负责版本任务进度同步的工作,需要在每个工作日下午5点在群里发消息,同步项目进度。这种重复劳动完全可以交给定时服务去做,然后通过webhook推送机器人就好了。

简单实现

第一个想到的可能就是setInterval,每隔1s执行一次函数,判断是否等于目标时间,执行对应的代码,如下:

点击查看【codepen】

setInterval 去跑定时任务的问题
时间越长,偏差越大

  1. var date = new Date().getTime()
  2. let i = 0
  3. let gap = 0
  4. var timer = setInterval(function() {
  5. i++
  6. var later = new Date().getTime()
  7. gap += (later - date) / i
  8. console.log(gap)
  9. }, 1000)

setInterval 的运行机制可能和你理解的不太一样


我们每100毫秒调用一次func函数,如果func的执行时间少于100毫秒的话,在遇到下一个100毫秒前就能够执行完:

node定时服务 node-schedule 源码分析 - 图1;

但如果func的执行时间大于100毫秒,该触发下一个func函数时之前的还没有执行完怎么办?答案如下图所示,那么第二个func会在队列(这里的队列是指event loop)中等待,直到第一个函数执行完

node定时服务 node-schedule 源码分析 - 图2;

如果第一个函数的执行时间特别长,在执行的过程中本应触发了许多个func怎么办,那么所有这些应该触发的函数都会进入队列吗?
不,只要发现队列中有一个被执行的函数存在,那么其他的统统忽略。如下图,在第300毫秒和400毫秒处的回调都被抛弃,一旦第一个函数执行完后,接着执行队列中的第二个,即使这个函数已经“过时”很久了。

node定时服务 node-schedule 源码分析 - 图3;

还有一点,虽然你在setInterval的里指定的周期是100毫秒,但它并不能保证两个函数之间调用的间隔一定是一百毫秒。在上面的情况中,如果队列中的第二个函数时在第450毫秒处结束的话,在第500毫秒时,它会继续执行下一轮func,也就是说这之间的间隔只有50毫秒,而非周期100毫秒

setInterval的替代方案

setTimeout 的递归调用

  1. var date = new Date().getTime()
  2. let i = 0
  3. let gap = 0
  4. var timer = setTimeout(function() {
  5. i++
  6. var later = new Date().getTime()
  7. gap += later - (date + i * 1000)
  8. console.log(gap)
  9. timer = setTimeout(arguments.callee, 1000)
  10. }, 1000)

好像还是没有办法解决每秒计时产生的累加精度问题,长时间的定时任务会不会崩溃或者引起内存泄漏的问题。

node-schedule

https://github.com/node-schedule/node-schedule

Node Schedule是用于Node.js的灵活的cron类而非cron类作业调度程序。它允许您使用可选的重复规则来计划作业(任意函数)在特定日期执行。它在任何给定时间仅使用一个计时器(而不是每秒钟/分钟重新评估即将到来的作业)。

cron 表达式语法
http://www.imooc.com/article/73989

带着问题找方法

  1. node-schedule 如何解决定时服务精度问题
  2. node-schedule 如何长时间运行

node-schedule 分析

创建一个定时任务
**

  1. schedule.scheduleJob([?name], [?spec], [?method], [?callback])
  1. //example 2pamarams
  2. var date = new Date(2018, 4, 8, 21, 13, 0);
  3. var j = schedule.scheduleJob(date, function(){
  4. console.log('Time for tea!');
  5. });

这步会创建一个任务(job)保存该任务的回调,同时将该时间假如到定时器中 job.schedule

  1. var job = new Job(name, method, callback);
  2. if (job.schedule(spec)) {
  3. return job;
  4. }
  • job 任务的回调和操作
  • Invocation 任务的起止时间、重复规则和回调
  • prepareNextInvocation 执行队列
  • runonDate 只执行定时任务

job.schedule 根据给定时间初始化任务

//每一个具体要执行的任务
job

//定时器和任务的起止时间
Invocation

//长时间timeout
long-timeout

refer:
http://qingbob.com/difference-between-settimeout-setinterval/