面试中经常会出现,要求实现控制并发队列的题目,入参为异步任务、并发数量,掘金上有很多实现,但是过于优雅;今天从0到1写出 promise 并发队列
该示例使用 class 实现,这里先把使用代码po出来,下面会聚焦于 Queue 的实现
function delay(text,time){
return new Promise((resolve,reject) => {
setTimeout(() => {
resolve(text)
},time)
})
}
const p1 = () => delay('1',5000)
const p2 = () => delay('2',2000)
const p3 = () => delay('3',3000)
const p4 = () => delay('4',2000)
const p5 = () => delay('5',3000)
const p = [p1,p2,p3,p4,p5]
class Queue {
...
}
new Queue(p, 3)
Queue 功能分析
目前我们仅考虑在初始化时,设置异步任务数组和并发数量(limit),所以在初始化时,可以直接推入limit个异步任务到队列中,随即执行异步任务;
然后在运行异步过程中,每当一个异步任务结束,就推一个新的异步任务进入队列
理论可行,进行实践
初始化
在constructor中,除了赋值以外,还需要从异步任务的头部开始,推入limit个任务进入队列中,一旦推入完成,这里我们默认直接进行异步任务的执行;
class Queue {
constructor(initArr, limit) {
this.queue = [];
this.limit = limit;
this.lists = initArr;
for (let i = 0; i < this.limit; i++) {
this.queue.push(this.lists.shift())
}
this.work();
}
work() {
...
}
limit = 0;
queue = [];
lists = [];
}
work的定义
work的功能是执行当前队列(queue)的所有任务,并在其中异步任务完成时,将异步任务数组(lists)中的第一个拿出来传入队列(queue)中;
pop() {
if (this.lists.length > 0) {
this.queue.push(this.lists.shift());
}
}
work() {
while(this.queue.length > 0) {
const requestFunc = this.queue.shift();
const request = requestFunc();
Promise.resolve(request).finally(res => {
this.pop();
this.work();
})
}
}
这里使用Promise.resolve 保证request不是promise,代码也可以正常执行。
完整代码
class Queue {
constructor(initArr, limit) {
this.queue = [];
this.limit = limit;
this.lists = initArr;
for (let i = 0; i < this.limit; i++) {
this.queue.push(this.lists.shift())
}
this.work();
}
pop() {
if (this.lists.length > 0) {
this.queue.push(this.lists.shift());
}
}
work() {
while(this.queue.length > 0) {
const requestFunc = this.queue.shift();
const request = requestFunc();
Promise.resolve(request).finally(res => {
this.pop();
this.work();
})
}
}
limit = 0;
queue = [];
lists = [];
}
进阶版
进阶版增加一个能够在使用时,增加任务的功能,例如
const q = new Queue(p, 3)
q.push(() => delay('6', 3000))
所以相比上一个版本,新增一个外部调用的 push 方法;可以向异步队列中推入数组,存在两种情况,一种是可以直接推入队列(queue)中,一种是进入数组中等待调用;所以在push方法中只推入到数组中,同时新增一个pushQueue来处理是否推入队列(queue);
push(item) {
if (item instanceof Array) {
this.lists.push(...item);
} else {
this.lists.push(item)
}
this.pushQueue()
}
pushQueue的方法定义为,检查当前是否能够插入到队列中,如果可以,则插入,否则不执行操作;
pushQueue() {
if (this.lists.length > 0) {
for (let i = 0; i < this.limit - this.running; i++) {
const item = this.lists.shift()
if (item) this.queue.push(item);
}
}
}
这里新增了一个running变量,用来检查当前正在执行的异步任务数量,所以相应的在work方法中,需要在异步任务开始前和开始后,做running变量的增/减
work() {
while(this.queue.length > 0) {
const requestFunc = this.queue.shift();
const request = requestFunc();
// 此处增加
this.running += 1;
Promise.resolve(request).finally(res => {
// 此处减少
this.running -= 1;
// 这里也不是做简单的pop,而是通过pushQueue进行队列的处理
this.pushQueue();
this.work();
})
}
}
完整代码为
class Queue {
constructor(initArr, limit) {
this.queue = [];
this.limit = limit;
this.lists = initArr;
this.pushQueue();
this.work();
}
pushQueue() {
if (this.lists.length > 0) {
for (let i = 0; i < this.limit - this.running; i++) {
const item = this.lists.shift()
if (item)
this.queue.push(item);
}
}
}
push(item) {
if (item instanceof Array) {
this.lists.push(...item);
} else {
this.lists.push(item)
}
this.pushQueue()
}
work() {
s while(this.queue.length > 0) {
const requestFunc = this.queue.shift();
const request = requestFunc();
this.running += 1;
Promise.resolve(request).finally(res => {
this.running -= 1;
this.pushQueue();
this.work();
})
}
}
limit = 0;
queue = [];
lists = [];
running = 0
}