宝子们👋,今天来给大家分享任务队列执行的那些事儿🧐。
任务队列在异步任务处理中就像一个贴心的小管家,能让我们的程序运行得更有序、高效💪。
下面就跟着我一起来看看吧😎!
一、什么是任务队列✨
简单来说,任务队列本质上是一种数据结构。
队列用于存储待执行的任务(通常是函数),并按照特定的规则依次执行它们。
这些任务可以是各种异步操作,如网络请求、文件读取、定时器回调等,甚至也可以是同步函数,但在任务队列的框架下,它们都被视为需要排队处理的任务单元。
简单任务队列的思维导图:
二、任务队列执行的好处✨
1. 资源利用大优化🎯
任务队列就像是交通指挥员🚦,能控制同时 “行驶” 的任务数量,避免资源 “交通堵塞”。
比如,当我们有一堆网络请求任务时,如果同时发起太多,可能会把网络带宽占满,导致其他任务 “饿死”。但有了任务队列,我们可以合理设置并发数,让资源分配更均衡🤗。
2. 任务顺序随心控🌈
有些任务就像多米诺骨牌🧩,必须按顺序执行才能保证结果正确。
任务队列就能确保这些任务依次倒下,不会乱套。比如说数据处理流程中,前一个任务的数据处理结果是后一个任务的输入,任务队列可以保证这种依赖关系稳稳地被处理好👍。
3. 系统稳定有保障🛡️
想象一下,如果没有任务队列,大量异步任务像一群无头苍蝇乱撞😵。
而任务队列通过限制并发、处理超时和错误等操作,就像给系统穿上了一层坚固的铠甲,让它能从容应对各种复杂情况,稳定性和可靠性大幅提升👏。
4. 任务管理超轻松🤝
接下来会介绍任务管理的解析🛠️,搭配成熟队列执行库操作更丝滑。
我们可以轻松地添加、暂停、继续、掌控任务,而且还能实时监控任务状态,比如任务开始了、完成了还是出错了,就像给每个任务都装上了小监控摄像头📹。
三、手写 SimpleQueue 初体验✨
我们先上手简单手写一个队列 “小助手” SimpleQueue🧑✈️。
它是一个简单的任务队列实现,同样能提供一系列实用的方法来管理任务队列的执行。
1. 朴实无华的代码实现🚶♂️🚶♀
class SimpleQueue {
constructor() {
this.list = []; // 存储任务的数组
this.index = 0; // 当前任务执行下标
this.isStop = false; // 暂停标识
}
next(arg) {
// 若已经执行完最后一个或当前是暂停状态则不执行
if (this.index >= this.list.length - 1 || this.isStop) return;
const cur = this.list[++this.index];
cur(this.next, arg);
}
add(fn) {
// 支持添加一个或多个任务
if (Array.isArray(fn)) {
this.list.push(...fn);
} else {
this.list.push(fn);
}
}
run() { // 按序执行队列里的任务
const cur = this.list[this.index];
if (typeof cur === 'function') {
cur(this.next);
}
}
stop() { // 暂停
this.isStop = true;
}
retry() { // 继续
this.isStop = false;
this.run();
}
goOn() { // 跳过,执行下一个
this.isStop = false;
this.next();
}
repeat() { // 重复
this.index = -1;
this.next();
}
removeAll() { // 清空队列
this.list.length = 0;
this.index = 0;
}
}
2. 让我们添加任务运行起来🎯
通过 const queue = new SimpleQueue();,我们轻松搭建起任务队列的框架🚪。
const queue = new SimpleQueue();
const task1 = (next) => {
console.log('任务 1 同步执行');
setTimeout(() => {
console.log('任务 2 异步完成');
next();
}, 1000);
};
const task2 = (next) => {
console.log('任务 3 完成');
setTimeout(() => {
console.log('任务 4 异步完成');
next();
}, 1500);
};
queue.add(task1);
queue.add(task2);
queue.run();
// 任务将会按任务1-4依次完成
队列控制:灵活掌控任务流程
- 暂停与继续:
queue.stop()和queue.retry()为我们提供了对队列执行节奏的精准控制。当需要暂时中断任务执行时,queue.stop()就像按下了暂停键⏸️,让队列静止在当前状态;而queue.retry()则如同再次按下播放键▶️,使队列从暂停的地方恢复执行,确保任务流程的连贯性。 - 跳过任务:
queue.goOn()赋予我们快速跳过当前任务的能力,如同在播放列表中直接跳到下一首歌曲⏩,让队列直接进入下一个任务的执行阶段,适用于某些情况下需要紧急处理后续任务的场景。 - 重复队列:
queue.repeat()则像是一个神奇的重置按钮,它将队列的任务索引归零,让整个队列重新开始执行,就像重新播放一段精彩的视频🎬,提供多次执行相同任务序列的便利。
三、进阶之选: p-queue的奇妙世界🌟
1. p-queue 初印象🌈
当我们的任务需求变得更复杂时,在github上总能找到属于你的曙光🦸♂️ !
拥有更强大的功能,能更好地应对各种复杂的异步任务场景的 p-queue 可能帮到你。
p-queue 是强大的任务队列库:
有并发控制,合理分配资源;支持任务优先级,精确调度;
有超时处理,防任务阻塞。还有丰富事件通知,可实时监控。
能灵活添加管理任务、支持队列操作,且扩展性和兼容性强,助力异步任务处理。
2. 话不多说,使用示例请看VCR🎬
import PQueue from 'p-queue';
// 创建一个p-queue实例,设置并发数为2,每个任务超时时间为2秒
const queue = new PQueue({concurrency: 2, timeout: 2000});
// 定义默认优先级任务
const mediumPriorityTask = async (taskId) => {
console.log(`普通任务 ${taskId} 执行开始`);
// 该异步执行可能会出现超过2秒的情况
const randomTime = Math.floor(Math.random() * 1000) + 1500
await new Promise((resolve) => setTimeout(resolve, randomTime)
console.log(`普通任务 ${taskId} 执行完毕`);
return `普通任务 ${taskId} 结果`;
};
// 🎯添加单个默认优先级任务1,并处理其结果或错误
queue.add(() => mediumPriorityTask(1))
.then((result) => console.log('当前任务返回结果', result))
.catch((error) => console.error('当前任务的报错', error));
// 📦添加任务2和3,并处理整体结果或错误,这是一组小包裹,一起处理它们的情况😏
queue.addAll([
() => mediumPriorityTask(2),
() => mediumPriorityTask(3)
]).then((results) => console.log('两个任务完成后返回结果:', results))
.catch((error) => console.error('两个任务中里有执行错误:', error));
// 🚀添加高优先级任务4,设置优先级为99,这可是超级VIP任务。
queue.add({
task: () => mediumPriorityTask(4),
priority: 99
});
// 🚦手动启动队列
queue.start();
// 暂停队列
setTimeout(() => {
queue.pause();
console.log('⏸️队列刹车了');
}, 1200);
// 一段时间后继续队列
setTimeout(() => {
queue.start();
console.log('🚗队列继续发车');
}, 2500);
// 监听队列空事件
queue.onEmpty().then(() => console.log('队列执行完啦'));
// 监听队列空闲事件
queue.onIdle().then(() => console.log('队列目前在摸鱼'));
// 🗑️清空队列
setTimeout(() => {
queue.clear();
console.log('队列任务被哔掉了');
}, 5000);
// 📊获取队列大小相关信息
console.log('队列中等待执行的任务数量:', queue.size);
console.log('正在执行的任务数量:', queue.pending);
console.log('队列是否暂停:', queue.isPaused);
在这个示例中:
- 首先创建了一个
p-queue实例,设置了并发数和超时时间。 - 定义需要执行的任务函数。
- 使用
add方法添加单个任务,并通过then和catch处理当前添加的任务回调处理。 - 使用
addAll方法添加多个任务,并处理它们的整体结果和错误。 - 通过
setTimeout在特定时间暂停和继续队列。 - 监听
onEmpty和onIdle事件。 - 在一定时间后清空队列。
- 最后获取并打印队列数量和队列暂停状态等信息。
四、 爱之再判断
虽说 SimpleQueue 能实现基本的任务排队,但跟人家 p-queue 相对比:
屁股上挂暖壶——有一腚的水瓶(平)😅!
(1)并发控制
- p-queue:能通过
concurrency精准设置同时执行的任务数量,合理控制任务流。 - SimpleQueue:一旦遇到耗时任务,就像堵住的水管🚿,响应超慢。
(2)任务优先级
- p-queue:每个任务都能被赋予优先级数值,数字越大越优先执行。
- SimpleQueue:所有任务平等,按照先来后到的顺序排队,一视同仁没有特殊待遇。
(3)超时处理
- p - queue:通过
timeout为任务设定最长执行时间,考试时间到了🕙,没做完也得停笔,这样能防止任务一直霸占资源,保证队列高效运行。 - SimpleQueue:任务执行起来就像脱缰的野马🐎,一旦卡住或耗时过长,整个队列都得跟着遭殃。
(4)事件通知
- p-queue:提供各种任务执行状态的事件通知,如任务开始、完成、出错、队列空和空闲等。
- SimpleQueue:相对 “安静” 许多,仅靠任务函数内部手动调用
next来推进任务。
(5)代码维护与扩展
- p-queue:模块化设计,丰富的配置选项和灵活接口,让开发者能轻松扩展和定制,比如定制特殊队列存储或调度策略,适应各种场景,维护起来也得心应手😎。
- SimpleQueue:实现简单直接,代码逻辑单一,遇到复杂需求,可能会出现小马拉大车。
(6)错误处理
- p-queue:能捕获任务执行中的错误,还能根据配置处理超时错误等。通过
add和addAll返回的 Promise统一处理任务错误。 - SimpleQueue:没有统一的错误处理机制,错误处理分散在任务函数内部。
对比总结:爱之终抉择
手写的SimpleQueue
- 优点:代码简单易懂,适合新手,基本功能齐全,能满足简单场景下的任务排队、暂停、继续等操作,轻量级,占用资源少。
- 缺点:缺乏并发控制、任务优先级、超时处理,事件通知不完善,可维护和扩展性差,错误处理分散。
p-queue
- 优点:有并发控制、任务优先级、超时处理,事件通知丰富,可灵活添加和管理任务,扩展性和兼容性强,能统一处理错误。
- 缺点:对于超简单场景,使用它可能有些 “大材小用”,学习成本也还好。
🎉探索任务队列的奇妙之旅就到这里啦!
宝子们,今天我们一起体验了 p-queue 的各种强大功能,是不是感觉我上我也能实现 😎?
下一篇笔记我会带着大家深入剖析 p-queue 的源码,看看它内部到底是怎么运作的,让我们可以更好地掌控这个强大的工具。
感谢大家的阅读,我们下一篇笔记见啦!💕