JS如何实现并发控制?

438 阅读2分钟

这是我参与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()