1. 题目要求
最近遇到了一个 JavaScript 手写代码题,要求实现一个具有并发数量限制的异步任务调度器,可以规定最大同时运行的任务。
实现一个Scheduler
类,使下面的代码能正确输出。
// 延迟函数
const sleep = time => new Promise(resolve => setTimeout(resolve, time));
// 同时进行的任务最多2个
const scheduler = new Scheduler(2);
// 添加异步任务
// time: 任务执行的时间
// val: 参数
const addTask = (time, val) => {
scheduler.add(() => {
return sleep(time).then(() => console.log(val));
});
};
addTask(1000, '1');
addTask(500, '2');
addTask(300, '3');
addTask(400, '4');
// 2
// 3
// 1
// 4
2. 思路及实现
设计Scheduler
类,要具有并发数量限制功能,需要定义最大可并发任务数max
,从入参中获取。并定义当前并发任务数count
,表示正在执行的任务数量。另外,定义一个待执行的任务队列queue
。
在添加任务add
函数中,首先判断当前正在执行的任务数,若当前正在执行的任务数达到最大容量max
,则当前的任务需要阻塞在此处。具体做法为,new
一个Promise
对象,将resolve
函数的引用推入队列queue
中。只要resolve
函数没有被执行,当前任务就会一直阻塞在这里。
若当前正在执行的任务数没有达到最大容量,那么对count
进行加一,执行当前函数fn
,并拿到返回值res
,执行完毕后count
减一。此时若队列queue
中有值,说明之前有任务因为并发数量限制而被阻塞,将队头的resolve
弹出,并执行。执行resolve
之后,之前阻塞的任务就可以正常执行了。
最后返回fn
函数执行的结果res
。
具体代码如下:
class Scheduler {
constructor(max) {
// 最大可并发任务数
this.max = max;
// 当前并发任务数
this.count = 0;
// 阻塞的任务队列
this.queue = [];
}
async add(fn) {
if (this.count >= this.max) {
// 若当前正在执行的任务,达到最大容量max
// 阻塞在此处,等待前面的任务执行完毕后将resolve弹出并执行
await new Promise(resolve => this.queue.push(resolve));
}
// 当前并发任务数++
this.count++;
// 使用await执行此函数
const res = await fn();
// 执行完毕,当前并发任务数--
this.count--;
// 若队列中有值,将其resolve弹出,并执行
// 以便阻塞的任务,可以正常执行
this.queue.length && this.queue.shift()();
// 返回函数执行的结果
return res;
}
}
3. 测试
class Scheduler {
...
}
const sleep = time => new Promise(resolve => setTimeout(resolve, time));
const scheduler = new Scheduler(2);
const addTask = (time, val) => {
scheduler.add(() => {
return sleep(time).then(() => console.log(val));
});
};
addTask(1000, '1');
addTask(500, '2');
addTask(300, '3');
addTask(400, '4');
// 2
// 3
// 1
// 4
如上代码所示,并发任务数量最大为2
。添加了4
个异步任务,所需要执行的时间分别为1000 ms
、500 ms
、300 ms
、400 ms
。
由于并发任务数量最大为2
,最初执行任务1
和2
。任务2
经过500 ms
后执行完毕,输出'2'
。接着执行任务3
,又经过300 ms
后,输出'3'
。任务1
仍然在执行,任务4
开始执行。到了1000 ms
时刻,任务1
执行完毕,输出'1'
。最后任务4
执行完,输出'4'
。
符合并发数量限制,达到了设计要求。
将执行过程表示在时间轴上,如下图所示:
以上是本人学习所得之拙见,若有不妥,欢迎指出交流!