本文详细介绍 Node.js 中实现定时任务与队列处理的两种核心方案。通过 node-schedule 库演示 Cron 风格、对象语法及基于日期的定时器配置与取消方法,并结合 async 库的 queue 模块讲解如何限制并发 Worker 数量以顺序执行耗时任务,适用于数据导出、消息推送等场景。
Node.js 定时任务与队列处理实战:node-schedule 和 async 详解
一、介绍与需求
1.1、介绍
node-schedule 是 Node.js 生态中一款功能灵活的作业调度库,支持类 Cron 表达式与非 Cron 风格的定时任务定义。它允许开发者通过可选的递归规则,将任意函数安排在指定的日期和时间执行。该库在运行时仅维护一个计时器,而非每秒或每分钟重新评估所有待执行作业,从而在保证灵活性的同时优化了资源消耗。
async 库提供了强大的异步 JavaScript 流程控制工具。其中的 queue(队列)模块是一种并行流程控制器,与 parallel 模块不同,queue 允许开发者精确控制同时执行的函数数量(Worker 数量),而 parallel 则是无限制地并行执行所有传入的函数。
1.2、需求
在实际 Node.js 项目开发中,定时任务处理场景十分常见。例如:定期导出业务数据、定时向用户发送消息或邮件通知、定期备份特定类型的文件等。特别是在批量发送消息时,接收用户可能多达数百甚至数千个,此时若直接并行处理可能导致服务器压力过大或资源耗尽,因此需要借助队列机制来顺序或限流执行这些任务。
定时器实现
二、定时器实现
第一步:安装 node-schedule 依赖库。
npm install node-schedule --save
第二步:封装定时器模块,便于项目复用。
const schedule = require('node-schedule');//定时器
const nodeTimer = {};
let cancelTimer = ''
/**
*Cron风格定时器/对象文本语法定时器
* @param executionTime :定时器字符串'30 * * * * *'/定时器对象{hour: 16, minute: 11, dayOfWeek: 1}
* @param callback :回调函数
*/
nodeTimer.scheduleTimer = (executionTime = '30 * * * * *', callback) => {
// 每分钟的第30秒触发: '30 * * * * *'
// 每小时的1分30秒触发 :'30 1 * * * *'
// 每天的凌晨1点1分30秒触发 :'30 1 1 * * *'
// 每月的1日1点1分30秒触发 :'30 1 1 1 * *'
// 2016年的1月1日1点1分30秒触发 :'30 1 1 1 2016 *'
// 每周1的1点1分30秒触发 :'30 1 1 * * 1'
cancelTimer = schedule.scheduleJob(executionTime, () => {
if (typeof callback === 'function') {
callback()
}
});
}
module.exports = nodeTimer;
第三步:在业务代码中调用封装好的定时器模块。
在回调函数中编写具体需要执行的业务逻辑代码,至此一个完整的定时器功能即告完成。
引入封装好的定时器模块:
const nodeTimer = require('./node_timer.js');
1、Cron 风格定时器
规则参数详解:星号(*)代表通配符。
* * * * * * ┬ ┬ ┬ ┬ ┬ ┬ │ │ │ │ │ │ │ │ │ │ │ └ day of week (0 - 7) (0 or 7 is Sun) │ │ │ │ └───── month (1 - 12) │ │ │ └────────── day of month (1 - 31) │ │ └─────────────── hour (0 - 23) │ └──────────────────── minute (0 - 59) └───────────────────────── second (0 - 59, OPTIONAL)
上述六个占位符从左至右依次代表:秒、分、时、日、月、周几。
星号(*)表示通配符,匹配任意值。例如,当秒位置为 * 时,表示任意秒数均会触发任务,其他位置同理。
// 每分钟的第30秒触发: '30 * * * * *'
// 每小时的1分30秒触发 :'30 1 * * * *'
// 每天的凌晨1点1分30秒触发 :'30 1 1 * * *'
// 每月的1日1点1分30秒触发 :'30 1 1 1 * *'
// 2016年的1月1日1点1分30秒触发 :'30 1 1 1 2016 *'
// 每周1的1点1分30秒触发 :'30 1 1 * * 1'
// 每分钟的1-10秒都会触发,其它通配符依次类推 :'1-10 * * * * *'
调用定时器示例:
nodeTimer.scheduleTimer('30 * * * * *',function(err){
if(!err){
console.log('scheduleTimer:' + new Date());
}
});
运行效果:

2、对象文本语法定时器
- second (0-59)
- minute (0-59)
- hour (0-23)
- date (1-31)
- month (0-11)
- year
- dayOfWeek (0-6) Starting with Sunday
//每周一的下午15:03:30触发,其它组合可以根据我代码中的注释参数名自由组合
nodeTimer.scheduleTimer({hour: 15, minute: 3, second: 30},function(err){
if(!err){
console.log('scheduleTimer:' + new Date());
}
});
运行效果:

3、基于日期的定时器
var date = new Date(2019, 01, 07, 15, 03, 30);
nodeTimer.scheduleTimer(date,function(err){
if(!err){
console.log('scheduleTimer:' + new Date());
}
});
4、递归规则定时器
其参数配置与对象文本语法定时器的参数类似。
var rule = new schedule.RecurrenceRule();
rule.dayOfWeek = [0, new schedule.Range(4, 6)];//每周四,周五,周六执行
rule.hour = 15;
rule.minute = 0;
nodeTimer.scheduleTimer(rule,function(err){
if(!err){
console.log('scheduleTimer:' + new Date());
}
});
5、取消定时器
// 取消定时器
// 调用 定时器对象的cancl()方法即可
nodeTimer.scheduleCancel = () => {
// 定时器取消
cancelTimer.cancel();
console.log('定时器成功取消');
}
调用方法:
nodeTimer.scheduleCancel()
运行效果:

队列处理
三、队列处理
第一步:安装 async 库。
npm install --save async
第二步:封装队列处理模块。
queue 模块可视为 enhanced parallel(增强版并行控制器),其核心区别在于限制了 Worker 的数量,不再一次性全部执行所有任务。当现有 Worker 全部忙碌时,新加入的任务将进入队列等待,直到有可用的 Worker 资源。
该函数提供了多个回调钩子,例如:当 Worker 数量即将用尽时触发 saturated 回调、当队列中无等待任务时触发 paused 回调、以及当所有任务执行完毕时触发 drain 回调。
const async = require('async');
/**
*队列
* @param obj :obj对象 包含执行时间
* @param callback :回调函数
*/
const nodeQueue = async.queue(function (obj, callback) {
setTimeout(function () {
// 需要执行的代码的回调函数
if(typeof callback==='function'){
callback();
}
}, obj.time)
}, 1)
// worker数量将用完时,会调用saturated函数
nodeQueue.saturated = function() {
console.log('all workers to be used');
}
// 当最后一个任务交给worker执行时,会调用empty函数
nodeQueue.empty = function() {
console.log('no more tasks wating');
}
// 当所有任务都执行完时,会调用drain函数
nodeQueue.drain = function() {
console.log('all tasks have been processed');
}
module.exports = nodeQueue;
第三步:调用封装好的队列方法。
const nodeQueue = require('./node_queue.js');
for (let i = 0; i < 10; i++) {
nodeQueue.push({ name: 1, time: 2000 }, function (err) {
console.log('队列执行错误信息==',err);
if(!err){
// 需要执行的代码或函数
console.log('需要执行的代码或函数第',i+1,'个')
}
})
};
运行效果:

总结
以上就是本文的全部内容,希望本文关于 Node.js 定时器和队列处理的内容对大家的学习或工作具有一定的参考与指导价值。如有疑问欢迎留言交流,感谢支持。