这是我参与8月更文挑战的第2天,活动详情查看:8月更文挑战
背景
在开发过程中,有时会遇到需要控制任务并发执行数量的需求,避免由于请求过于频繁.
例如一个爬虫程序,可以通过限制其并发任务数量来降低请求频率,从而避免由于请求过于频繁被封禁问题的发生。
接下来,本文介绍如何实现一个并发控制器。
class TaskManage{
constructor(max=5){
this.max = max; //最大并发数
this.queue = []; //任务队列
this.count = 0; //正在执行的任务
}
//添加任务
addTask(fn){
return new Promise((resolve)=>{
this.queue.push({fn, resolve});
this.request()
})
}
//请求递归封装
request(){
if(!this.queue || !this.queue.length || this.count>=this.max){
return ;
}
this.count++;
let task = this.queue.shift();
task.fn().then((data)=>{
this.count--;
task.resolve(data)
this.request()
});
}
}
//模拟日常任务
const task = (x) => {
return ()=>new Promise(res=>{
setTimeout(()=>{
res(x*1000)
},x*1000)
})
}
//创建示例
let a = new TaskManage()
//添加任务
a.addTask(task(5)).then(res=>console.log(res));
a.addTask(task(2)).then(res=>console.log(res));
a.addTask(task(3)).then(res=>console.log(res));
a.addTask(task(1)).then(res=>console.log(res));
a.addTask(task(4)).then(res=>console.log(res));
a.addTask(task(2)).then(res=>console.log(res));
为了控制Promise的数量,我们采用max变量定义了最大的并发数量。
由于要求最后可以采用链式调用,采用queue队列的数据结构来存储task。
addTask函数返回的是一个Promise,所以可以再后面.then。 只需将要执行的task和执行后的回调resolve存入队列中,即可再合适的地方来调用。
request主要是一个递归辅助函数。 对于递归函数而言,肯定是做好三要素的编写:
1. 一定有一种可以退出程序的情况:
当队列中没有任务的时候或任务数已达到最大并发数的时候,我们将不再递归。只将任务放入队列里,而不进行request。
if(!this.queue || !this.queue.length || this.count>=this.max){
return ;
}
2. 总是在尝试将一个问题化简到更小的规模,明确函数的主体:
若并发数未到最大并发,则首先要将当前并发数加一。 并从队列中取出一个任务。 最后完成任务,将结果返回给addTask里的resolve
this.count++;
let task = this.queue.shift();
task.fn().then((data)=>{
this.count--;
task.resolve(data)
this.request()
});
3. 找出函数的等价关系式:
类似线程的感觉,执行完当前任务,立即尝试进行下一次的request。 this.request()