前言
在 Javascript 中退腥,有時候我們有定時事務(wù)的需求,自己借助setTimeout和setInterval來實現(xiàn)的化太過麻煩温兼,node-schedule是一個非常不錯的npm包,可以幫助我們快速的創(chuàng)建和管理定時事務(wù)。
本文主要介紹 node-schedule 的基礎(chǔ)用法比搭。
node-schedule介紹
安裝
npm install node-schedule
創(chuàng)建計劃
需要用到scheduleJob
函數(shù),會返回一個Job實例對象:
function scheduleJob(name: string, rule: ..., callback: function): schedule.Job
- name
任務(wù)名南誊,當(dāng)你沒有指定時身诺,它將以時間戳作為名字:'<Anonymous Job 1 2023-04-20T10:23:28.966Z>'
- rule:
任務(wù)調(diào)度的規(guī)則,支持多種形式的rule:- string - Cron表達式
- number
- schedule.RecurrenceRule
- Date
- callback
創(chuàng)建任務(wù)時的回調(diào)函數(shù)
可以通過scheduleJob(name, rule, callback)
或者scheduleJob(rule, callback)
創(chuàng)建計劃抄囚。
調(diào)度規(guī)則
基于Cron表達式[1]的規(guī)則
在 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時,建議寫完整幔托,可讀性比較好穴亏。
定時循環(huán)
cron表達式作為rule時,用來指定每當(dāng)某個/某些時刻觸發(fā)任務(wù)的調(diào)度執(zhí)行重挑,譬如:
- 每秒執(zhí)行
*
號代表的意思是每嗓化,因此這條rule代表:全年的每天每秒都觸發(fā)
const rule = '* * * * * *';
const job = schedule.scheduleJob(rule,()=>{});
- 每N秒執(zhí)行
*/1
[2]代表從頭開始,每1秒觸發(fā)一次
const rule = '*/1 * * * * *';
const job = schedule.scheduleJob(rule,()=>{});
- 每個半點時觸發(fā)谬哀,在第30分鐘內(nèi)的每秒執(zhí)行
const rule = '* 30 * * * *';
const job = schedule.scheduleJob(rule,()=>{});
- 每個半點15秒時觸發(fā)刺覆,在第30分鐘內(nèi)的每10秒執(zhí)行
const rule = '*/10 30 * * * *';
const job = schedule.scheduleJob(rule,()=>{});
- 每個1秒,3秒和9秒執(zhí)行
const rule = '1,3,9 * * * * *';
const job = schedule.scheduleJob(rule,()=>{});
- 每個星期日觸發(fā)史煎,全天每秒都執(zhí)行
0和7都是星期日谦屑,1-6對應(yīng)周一到周六,此時必須把日域用?
覆蓋篇梭,如果日用*
伦仍,意味著每天都觸發(fā),會覆蓋周日的限制[3]
const rule = '* * * ? * 0';
const job = schedule.scheduleJob(rule,()=>{});
- 每月第4個星期日觸發(fā)很洋,全體每秒都執(zhí)行
用#N
[4]在后面指定第幾個星期幾
const rule = '* * * ? * 0#4';
const job = schedule.scheduleJob(rule,()=>{});
- 每周一早上的零點執(zhí)行
const rule = '0 0 0 ? * 1';
const job = schedule.scheduleJob(rule,()=>{});
- 每0~10分內(nèi)充蓝,每分鐘的第0秒觸發(fā)
0-10
[5]代表這個范圍,并且是閉區(qū)間
const rule = '0 0-10 * * * *';
const job = schedule.scheduleJob(rule,()=>{});
- 每月的20日觸發(fā)
此時必須把星期域用?
覆蓋,如果周幾用*
谓苟,意味著每個周幾都觸發(fā)官脓,那就是每天,會覆蓋20日的限制
const rule = '* * * 20 * ?';
const job = schedule.scheduleJob(rule,()=>{});
注意:
- node-schedule中不支持cron的
L
和W
語法(最后和最近) - node-schedule中不支持Year涝焙,因此只有6個域卑笨,默認(rèn)每年
- 創(chuàng)建了計劃后,到點就會觸發(fā)了仑撞,Job沒有run,start之類的方法
- 到達觸發(fā)時間點時赤兴,node-schedule會以同樣的方式創(chuàng)建一個新Job
cron表達式很簡潔,不過可讀性也是比較低隧哮,每次看到都要思考一下桶良,我們來看一下別的rule寫法
基于Date的規(guī)則
假設(shè)您非常希望在一個精確到某一個時間點上的秒數(shù)的僅觸發(fā)一次的計劃,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的規(guī)則
可能有人還記得 rule 匹配的類型中number,其實它對應(yīng)的是時間戳榆鼠,如果接收到了number的rule纲爸,會被當(dāng)作時間戳來生成相應(yīng)的Date,接著通過基于Date的rule去創(chuàng)建計劃
基于RecurrenceRule的規(guī)則
如果你的任務(wù)是定時重復(fù)執(zhí)行的妆够,并且你希望有比cron更高的可讀性缩焦,你可以嘗試使用RecurrenceRule對象作為rule
- 創(chuàng)建RecurrenceRule對象
let rule = new RecurrenceRule();
Recurrence的構(gòu)造函數(shù):
function Recurrence(year, month, date, dayOfWeek, hour, minute, second, tz)
因此你也可以在構(gòu)造的時候就把參數(shù)傳進去,如果沒傳的责静,默認(rèn)是每
- 設(shè)置重復(fù)時刻
每個域可以賦的值和cron中基本類似袁滥,不過有一下幾點區(qū)別: - 除了tz,其他都必須是數(shù)字
- dayOfWeek 范圍是 0~6灾螃,不再支持7作為Sunday
- month 范圍是 0~11 而非1~12
- 不能像cron那樣設(shè)置第幾個周五這樣子
- 支持時區(qū)設(shè)置题翻,點擊查看所有可以作為tz的值
如果同一個域下可以有多個值,則須要把所有的值放在一個數(shù)組中賦給該域:
//每秒觸發(fā)
rule.second = [1,2,3,... ...,60];
如果只有兩三個值那還好腰鬼,像上面這樣的60個值手都麻了嵌赠。如果是連續(xù)的值,node-schedule提供了一個Range函數(shù)用于創(chuàng)建連續(xù)的元素:
rule.second = [0,1,2,new schedule.Range(10,20)]; //必須作為[]的元素熄赡,本身不是一個數(shù)組
更方便的創(chuàng)建
你還可以直接使用鍵值式對象當(dāng)作RecurrenceRule作為rule:
const job = schedule.scheduleJob(
{
hour: 14,
minute: 30,
dayOfWeek: 0
},
function (){
//...
}
);
基于RecurrenceSpecDateRange的規(guī)則
源碼里面沒看到姜挺,可能已廢棄
基于RecurrenceSpecObjLit的規(guī)則
源碼里面沒看到,可能已廢棄
結(jié)束計劃
調(diào)用 Job 實例中的 cancel 方法即可結(jié)束計劃的運行
job.cancel();
任務(wù)內(nèi)容
- 通常把任務(wù)的內(nèi)容寫在創(chuàng)建時的callback函數(shù)[6]內(nèi)
const job = schedule.scheduleJob("* * * * * *",()=>{
console.log("任務(wù)進行中...");
});
- 當(dāng)然你將任務(wù)內(nèi)容綁定在監(jiān)聽到Job運行完時觸發(fā)的函數(shù)內(nèi)也無傷大雅(狀態(tài)監(jiān)聽稍后講)
this.job.on("run",()=>{
console.log("任務(wù)進行中...");
console.log("任務(wù)結(jié)束");
});
不過這樣做的前提是彼硫,你不需要在任務(wù)內(nèi)容執(zhí)行完后返回一些相關(guān)的數(shù)據(jù)和信息炊豪。因此原則上規(guī)范的做法是凌箕,把任務(wù)的內(nèi)容寫在創(chuàng)建時的callback內(nèi)
狀態(tài)監(jiān)聽
總共有5個事件可以監(jiān)聽(其實創(chuàng)建也能算一個),不說什么了词渤,直接擺代碼:
//這個job被我存儲一個實例內(nèi)牵舱,所以是this.job
this.job = schedule.scheduleJob(this.rule,
//2.在執(zhí)行這里的回調(diào)
()=>{
console.log("任務(wù)運行...");
return "執(zhí)行了一件node-schedule任務(wù)"; //可以被監(jiān)聽success的回調(diào)函數(shù)捕捉
});
//1.先執(zhí)行 scheduled 回調(diào)
this.job.on("scheduled",()=>{
console.log("任務(wù)被調(diào)度");
//scheduled的回調(diào)函數(shù)中出的錯不會被監(jiān)聽error所捕獲
});
//3.再執(zhí)行 run 回調(diào)
this.job.on("run",()=>{
console.log("任務(wù)結(jié)束");
});
//4.再執(zhí)行 success 回調(diào)
this.job.on("success",(data)=>{
console.log(`任務(wù)成果: ${data}`);
console.log("任務(wù)成功!\n");
});
//5.只監(jiān)聽任務(wù)創(chuàng)建回調(diào),run回調(diào)和success回調(diào)中產(chǎn)生的異常
this.job.on("error",(err)=>{
console.log(`[error][${new Date().toLocaleString()}]${err.message}`);
});
//6.計劃被取消的那一刻執(zhí)行 canceled
this.job.on("canceled",()=>{
console.log("計劃結(jié)束!");
})
腳注
-
Cron表達式是一種被多個空格隔成多個域的字符串,每一個域由數(shù)字和特殊字符組成缺虐,代表一種含義芜壁,通常用于指定時間規(guī)則 ?
-
S/N
中S
表示從該域最起始的位置S處觸發(fā)一次,接著每隔N觸發(fā)一次高氮,5/20
慧妄,5觸發(fā),然后25剪芍,45... ? -
因為日域和周幾域都是關(guān)于天的規(guī)則塞淹,因此會產(chǎn)生沖突,此時就需要用
?
把其中一項的優(yōu)先級降低 ? -
#N
只能用于星期幾的那個域里面 ? -
-
代表范圍紊浩,并且是閉區(qū)域 ? -
這個函數(shù)可以傳入?yún)?shù),但說實在的疗锐,沒有必要 ?