如何控制并发任务 --前端
前言:
一位在大厂工作的朋友分享了一道公司的笔面试题,据他所述这道笔面试题是现在所有大厂所要考核的重心。在JavaScript中,我们经常需要控制并发任务的数量,以避免过多的请求导致性能下降,本文将介绍如何使用Promise和类来实现JavaScript的并发控制,先看需求代码:
function timeout(time) {
return new Promise((reslove) => {
setTimeout(() => {
reslove();
}, time);
});
}
class SuperTask{}
const superTask = new SuperTask();
function addTask(time, name) {
superTask
.add(() => timeout(time))
.then(() => {
console.log(`任务${name}完成`);
});
}
addTask(10000, 1);
addTask(5000, 2);
addTask(3000, 3);
addTask(4000, 4);
addTask(5000, 5);
/*
期望结果:
10000毫秒后输出 任务1完成
5000毫秒后输出 任务2完成
8000毫秒后输出 任务3完成
12000毫秒后输出 任务4完成
15000毫秒后输出 任务5完成
*/
阅读代码:
function timeout(time) {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, time);
});
}
通过阅读代码我们可以知道这个函数接收一个时间参数,返回一个Promise对象。当时间到达后,Promise对象将会被resolve
class SuperTask{}
const superTask = new SuperTask();
function addTask(time, name) {
superTask
.add(() => timeout(time))
.then(() => {
console.log(`任务${name}完成`);
});
}
addTask(10000, 1);
addTask(5000, 2);
addTask(3000, 3);
addTask(4000, 4);
addTask(5000, 5);
/*
期望结果:
10000毫秒后输出 任务1完成
5000毫秒后输出 任务2完成
8000毫秒后输出 任务3完成
12000毫秒后输出 任务4完成
15000毫秒后输出 任务5完成
*/
需要实现一个SuperTask类,还定义了一个addTask辅助函数来调用调用,接收time、name两个参数,调用了superTask类的add方法,用这个add执行了之前定义的timeout函数并把参数time传入进去,执行完毕后打印内容。听起来很简单,我们看一下期望结果中的的内容,第一条和第二条是没问题,到了第三条可就不一样了。就是说,同时只能存在两个异步任务,其他的异步任务需要等待有任务执行完毕后才可以执行,参考下图。
那我们就按照需求编写一下这个SuperTask类
SuperTask类的实现
简单来说,该类接受一个并发数量参数,包含一个任务队列和一个正在运行的任务数。它有一个add方法,用于添加任务到任务队列中。当任务队列中有任务时,它会依次运行所有任务,直到任务队列为空或者正在运行的任务数达到并发数量。
class SuperTask {
//首先我们来定义一下需要的东西
constructor:(parallelCount = 2) {
this.parallelCount = parallelCount; //并发数量
this.runningCount = 0; //正在运行的任务数
this.tasks = [];//任务队列
}
//通过需求代码得知,我们需要一个add函数,这个函数要返回一个Promise
add(task) {
return new Promise((resolve, reject) => {
/*
这里不能直接task(),有可能当前正在运行的异步任务已经到达两个了,那我们就给他全部放到tasks中
为什么要带上resolve和reject呢?
下面_run函数在处理调用,但是add函数的Promsie结束不了啊,我们就需要把他们一起放进任务队列来供_run函数使用
*/
this.tasks.push({ task, resolve, reject });
//单独写一个_run函数来处理调用
this._run()
});
}
//依次运行tasks队列里的所有任务
_run() {
//依次执行队列里的所有任务,当然要满足我们的需求,当前任务的数量要小于并发数量
while (this.runningCount < this.parallelCount && this.tasks.length) {
//拿到任务队列中的第一个任务,并把原任务队列中的第一个任务删除。
const { task, resolve, reject } = this.tasks.shift();
//当前运行任务增加进行记录
this.runningCount++;
//对异步任务进行调用
task()
.then(resolve, reject)
.finally(() => {
//每运行完一个任务,减少当前运行任务,重新启动_run函数运行下一个任务
this.runningCount--;
this._run();
});
}
}
}
这样我们就搞定了一个并发任务控制器,完结撒花~
总结
- this.tasks.push({ task, resolve, reject }) 的原因是因为当前状态下不确定是否可以运行任务,所以添加至任务队列让_run函数去进行任务调用,add的完成或者失败自身控制不了,所以要把reslove和reject一同放进队列中以便_run函数去控制
- 最后递归调用_run函数来依次调用任务队列中的任务。