优雅的使用node-schedule(上)

发布时间 2023-04-21 02:01:38作者: Evanpatchouli

前言

在 Javascript 中,有时候我们有定时事务的需求,自己借助setTimeout和setInterval来实现的化太过麻烦,node-schedule是一个非常不错的npm包,可以帮助我们快速的创建和管理定时事务。
本文主要介绍 node-schedule 的基础用法。

node-schedule介绍

安装

npm install node-schedule

创建计划

需要用到scheduleJob函数,会返回一个Job实例对象:

function scheduleJob(name: string, rule: ..., callback: function): schedule.Job
  • name
    任务名,当你没有指定时,它将以时间戳作为名字:'<Anonymous Job 1 2023-04-20T10:23:28.966Z>'
  • rule:
    任务调度的规则,支持多种形式的rule:
    • string - Cron表达式
    • number
    • schedule.RecurrenceRule
    • Date
  • callback
    创建任务时的回调函数

可以通过scheduleJob(name, rule, callback)或者scheduleJob(rule, callback)创建计划。

调度规则

基于Cron表达式[1]的规则

在 node-schedule 中,采用的 cron 表达式包含6个域:

*    *    *    *    *    *
┬    ┬    ┬    ┬    ┬    ┬
│    │    │    │    │    │
│    │    │    │    │    └ 星期几(相对于周日) (0 - 7) (0 or 7 is Sun)
│    │    │    │    └───── 月(相对于年初) (1 - 12)
│    │    │    └────────── 日(相对于月初) (1 - 31)
│    │    └─────────────── 时(相对于天初第0时) (0 - 23)
│    └──────────────────── 分(相对于时初第0分) (0 - 59)
└───────────────────────── 秒(相对于分出第0秒) (0 - 59, OPTIONAL)

使用cron字符串作为rule时,建议写完整,可读性比较好。

定时循环
cron表达式作为rule时,用来指定每当某个/某些时刻触发任务的调度执行,譬如:

  • 每秒执行
    * 号代表的意思是,因此这条rule代表:全年的每天每秒都触发
const rule = '* * * * * *';
const job = schedule.scheduleJob(rule,()=>{});
  • 每N秒执行
    */1 [2]代表从头开始,每1秒触发一次
const rule = '*/1 * * * * *';
const job = schedule.scheduleJob(rule,()=>{});
  • 每个半点时触发,在第30分钟内的每秒执行
const rule = '* 30 * * * *';
const job = schedule.scheduleJob(rule,()=>{});
  • 每个半点15秒时触发,在第30分钟内的每10秒执行
const rule = '*/10 30 * * * *';
const job = schedule.scheduleJob(rule,()=>{});
  • 每个1秒,3秒和9秒执行
const rule = '1,3,9 * * * * *';
const job = schedule.scheduleJob(rule,()=>{});
  • 每个星期日触发,全天每秒都执行
    0和7都是星期日,1-6对应周一到周六,此时必须把日域用 ? 覆盖,如果日用 * ,意味着每天都触发,会覆盖周日的限制[3]
const rule = '* * * ? * 0';
const job = schedule.scheduleJob(rule,()=>{});
  • 每月第4个星期日触发,全体每秒都执行
    #N [4]在后面指定第几个星期几
const rule = '* * * ? * 0#4';
const job = schedule.scheduleJob(rule,()=>{});
  • 每周一早上的零点执行
const rule = '0 0 0 ? * 1';
const job = schedule.scheduleJob(rule,()=>{});
  • 每0~10分内,每分钟的第0秒触发
    0-10 [5]代表这个范围,并且是闭区间
const rule = '0 0-10 * * * *';
const job = schedule.scheduleJob(rule,()=>{});
  • 每月的20日触发
    此时必须把星期域用 ? 覆盖,如果周几用 * ,意味着每个周几都触发,那就是每天,会覆盖20日的限制
const rule = '* * * 20 * ?';
const job = schedule.scheduleJob(rule,()=>{});

注意

  • node-schedule中不支持cron的 LW 语法(最后和最近)
  • node-schedule中不支持Year,因此只有6个域,默认每年
  • 创建了计划后,到点就会触发了,Job没有run,start之类的方法
  • 到达触发时间点时,node-schedule会以同样的方式创建一个新Job

cron表达式很简洁,不过可读性也是比较低,每次看到都要思考一下,我们来看一下别的rule写法

基于Date的规则

假设您非常希望在一个精确到某一个时间点上的秒数的仅触发一次的计划,Date是不错的选择

const schedule = require('node-schedule');
//2023年,4月,20日,23时,30分,0秒
const date = new Date(2023, 4, 20, 23, 30, 0);

const job = schedule.scheduleJob(date, ()=>{});
基于number的规则

可能有人还记得 rule 匹配的类型中number,其实它对应的是时间戳,如果接收到了number的rule,会被当作时间戳来生成相应的Date,接着通过基于Date的rule去创建计划

基于RecurrenceRule的规则

如果你的任务是定时重复执行的,并且你希望有比cron更高的可读性,你可以尝试使用RecurrenceRule对象作为rule

  • 创建RecurrenceRule对象
let rule = new RecurrenceRule();

Recurrence的构造函数:

function Recurrence(year, month, date, dayOfWeek, hour, minute, second, tz)

因此你也可以在构造的时候就把参数传进去,如果没传的,默认是每

//每秒触发
rule.second = [1,2,3,... ...,60];

如果只有两三个值那还好,像上面这样的60个值手都麻了。如果是连续的值,node-schedule提供了一个Range函数用于创建连续的元素:

rule.second = [0,1,2,new schedule.Range(10,20)];  //必须作为[]的元素,本身不是一个数组

更方便的创建
你还可以直接使用键值式对象当作RecurrenceRule作为rule:

const job = schedule.scheduleJob(
	{
		hour: 14,
		minute: 30,
		dayOfWeek: 0
	}, 
	function (){
		//...
	}
);
基于RecurrenceSpecDateRange的规则

源码里面没看到,可能已废弃

基于RecurrenceSpecObjLit的规则

源码里面没看到,可能已废弃

结束计划

调用 Job 实例中的 cancel 方法即可结束计划的运行

job.cancel();

任务内容

  • 通常把任务的内容写在创建时的callback函数[6]
const job = schedule.scheduleJob("* * * * * *",()=>{
	console.log("任务进行中...");
});
  • 当然你将任务内容绑定在监听到Job运行完时触发的函数内也无伤大雅(状态监听稍后讲)
this.job.on("run",()=>{
	console.log("任务进行中...");
	console.log("任务结束");
});

不过这样做的前提是,你不需要在任务内容执行完后返回一些相关的数据和信息。因此原则上规范的做法是,把任务的内容写在创建时的callback内

状态监听

总共有5个事件可以监听(其实创建也能算一个),不说什么了,直接摆代码:

//这个job被我存储一个实例内,所以是this.job
this.job = schedule.scheduleJob(this.rule,
//2.在执行这里的回调
()=>{
	console.log("任务运行...");
    return "执行了一件node-schedule任务"; //可以被监听success的回调函数捕捉
});

//1.先执行 scheduled 回调
this.job.on("scheduled",()=>{
	console.log("任务被调度");
	//scheduled的回调函数中出的错不会被监听error所捕获
});

//3.再执行 run 回调
this.job.on("run",()=>{
	console.log("任务结束");
});

//4.再执行 success 回调
this.job.on("success",(data)=>{
	console.log(`任务成果: ${data}`);
	console.log("任务成功!\n");
});

//5.只监听任务创建回调,run回调和success回调中产生的异常
this.job.on("error",(err)=>{
	console.log(`[error][${new Date().toLocaleString()}]${err.message}`);
});

//6.计划被取消的那一刻执行 canceled
this.job.on("canceled",()=>{
	console.log("计划结束!");
})

脚注


  1. Cron表达式是一种被多个空格隔成多个域的字符串,每一个域由数字和特殊字符组成,代表一种含义,通常用于指定时间规则 ↩︎

  2. S/NS 表示从该域最起始的位置S处触发一次,接着每隔N触发一次,5/20,5触发,然后25,45... ↩︎

  3. 因为日域和周几域都是关于天的规则,因此会产生冲突,此时就需要用 ? 把其中一项的优先级降低 ↩︎

  4. #N 只能用于星期几的那个域里面 ↩︎

  5. -代表范围,并且是闭区域 ↩︎

  6. 这个函数可以传入参数,但说实在的,没有必要 ↩︎