如何控制并发任务 --JavaScript前端

1,122 阅读3分钟

如何控制并发任务 --前端

前言:

​ 一位在大厂工作的朋友分享了一道公司的笔面试题,据他所述这道笔面试题是现在所有大厂所要考核的重心。在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传入进去,执行完毕后打印内容。听起来很简单,我们看一下期望结果中的的内容,第一条和第二条是没问题,到了第三条可就不一样了。就是说,同时只能存在两个异步任务,其他的异步任务需要等待有任务执行完毕后才可以执行,参考下图。

e965f20faaa116fcd8404dc92212b7e.png

那我们就按照需求编写一下这个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函数来依次调用任务队列中的任务。