如何封装一个带权的请求并发控制

184 阅读4分钟

首先理清思路

请求并发控制做的是,同时仅能有 <=某个个数的请求在网络中传输。

//模拟网络请求
function delay(delay) {
  return new Promise(res => {
    setTimeout(res, delay)
  })
}
//参数为最大请求并发数
const superTask = new SuperTask(2);
superTask.addTask(() => delay(2000), 4).then(() => console.log('Task 1 completed'));
superTask.addTask(() => delay(500), 5).then(() => console.log('Task 2 completed'));
superTask.addTask(() => delay(5000), 1).then(() => console.log('Task 3 completed'));

这段代码的输出结果:

Task 1 completed
Task 2 completed
Task 3 completed

首先先对任务进行排序,高优先级的先进性(权重越小,优先级越高)

//排序结果
Task3
Task1
Task2

所以先运行task3和task1,现在正在运行的任务为2和容量相同了,不能在运行任务了,需要等待正在执行的任务,执行完成才可以开启task2任务,等待2s后task1执行完成,开始执行执行task2,等待0.5s后task2执行完成,在等待2.5后task3执行完成。

要实现这个功能需要什么条件

  1. 需要一个可以自动对任务按照权重进行排序的数据结构

    使用heap堆(小根堆),当然也可以每次都对数组排序但是时间复杂较高(nlogn)而堆的存取时间复杂为logn

  2. 什么时候要执行任务

    用户调用addTask方法添加任务的时候去试探执行

    当任务完成得时候再去试探是否可以执行

  3. 什么条件可以执行任务

    堆还有任务

    正在执行的任务数量要小于容量

代码

先定义属性

class SuperTask {
  constructor(cap) {
    //容量
    this.cap = cap
    //小根堆,用来存储任务
    this.heap = new Heap((a, b) => a.priority - b.priority)
    //正在执行的任务数量
    this.runningCount = 0
  }

  //添加任务
  addTask(task, priority = 0) {
    return new Promise((res, rej) => {

    })
  }

  //执行任务
  __runtask() {
    
  }
}

我们看测试用法

superTask.addTask(() => delay(500), 5).then(() => console.log('Task 2 completed'));

addTask方法需要返回一个promise,并且promise的状态和task是同步的,这个要如何实现? 我们只需要在加添任务的时候,将promise的reslove,reject函数也添加进行即可,这样每个任务就和addTask方法的promise挂钩了。

addTask(task, priority = 0) {
    const p =  new Promise((res, rej) => {
      this.heap.push({
        task,
        res,
        rej,
        priority: priority
      })
    })
    this.__runtask()
    return p
}

再来实现一下__runtask函数,思路比较简单,只要可以执行,就一直递归调用__runtask方法执行。

  __runtask() {
      while (this.runningCount < this.cap && this.heap.size()) {
        const {task, res, rej} = this.heap.pop();
        this.runningCount++;
        task().then(res).catch(rej).finally(() => {
          this.runningCount--;
          this.__runtask();
        })
      }
  }

你觉这样就可以了吗,我们来试一下

import Heap from 'heap'

function delay(delay) {
  return new Promise(res => {
    setTimeout(res, delay)
  })
}

class SuperTask {
  constructor(cap) {
    this.cap = cap
    this.heap = new Heap((a, b) => a.priority - b.priority)
    this.runningCount = 0
  }

  addTask(task, priority = 0) {
    const p =  new Promise((res, rej) => {
      this.heap.push({
        task,
        res,
        rej,
        priority: priority
      })
    })
    this.__runtask()
    return p
  }

  __runtask() {
      while (this.runningCount < this.cap && this.heap.size()) {
        const {task, res, rej} = this.heap.pop();
        this.runningCount++;
        task().then(res).catch(rej).finally(() => {
          this.runningCount--;
          this.__runtask();
        })
      }
  }
}


const superTask = new SuperTask(2);
superTask.addTask(() => delay(2000), 4).then(() => console.log('Task 1 completed'));
superTask.addTask(() => delay(500), 5).then(() => console.log('Task 2 completed'));
superTask.addTask(() => delay(5000), 1).then(() => console.log('Task 3 completed'));

输出结果

image.png 这不对啊 正确结果不应该是

Task 1 completed
Task 2 completed
Task 3 completed

哪里错了呢,是因为我们执行addTask方法的时候直接就__runtask运行任务,任务还没来得及进行排序,那如何做到这个呢? 我们想要任务排序,就不能立即执行任务,需要等待任务全部进入到heap在去执行任务,我们可以利用事件循环机制,我们知道微任务 / 宏任务,都会等待主线程执行完成,主线程执行完成,heap中不就存储了全部任务了嘛。 修改代码:

import Heap from 'heap'

function delay(delay) {
  return new Promise(res => {
    setTimeout(res, delay)
  })
}

class SuperTask {
  constructor(cap) {
    this.cap = cap
    this.heap = new Heap((a, b) => a.priority - b.priority)
    this.runningCount = 0
  }

  addTask(task, priority = 0) {
    const p =  new Promise((res, rej) => {
      this.heap.push({
        task,
        res,
        rej,
        priority: priority
      })
    })
    setTimeout(() => {this.__runtask()}, 0)
    return p
  }

  __runtask() {
      while (this.runningCount < this.cap && this.heap.size()) {
        const {task, res, rej} = this.heap.pop();
        this.runningCount++;
        task().then(res).catch(rej).finally(() => {
          this.runningCount--;
          this.__runtask();
        })
      }
  }
}


const superTask = new SuperTask(2);
superTask.addTask(() => delay(2000), 4).then(() => console.log('Task 1 completed'));
superTask.addTask(() => delay(500), 5).then(() => console.log('Task 2 completed'));
superTask.addTask(() => delay(5000), 1).then(() => console.log('Task 3 completed'));

看输出: image.png 对了!!!

但是这样真的可以嘛,比如说我们有1000000个任务,那岂不是宏任务中要存储1000000个任务,但是真的有必要嘛,其实只需要有一次setTimeout(() => {this.__runtask()}, 0)就够了。 这个就很简单了,我们只需要搞一个变量,标志目前是否有setTimeOut回调即可。

补全代码;

  let flag = false
  addTask(task, priority = 0) {
    const p =  new Promise((res, rej) => {
      this.heap.push({
        task,
        res,
          rej,
          priority: priority
        })
      })
      if(!flag) {
        flag = true
        setTimeout(() => {
          this.__runtask()
          flag = false;
        }, 0)
      } 
      
      return p
    }

加个console.log验证一下:

import Heap from 'heap'

function delay(delay) {
  return new Promise(res => {
    setTimeout(res, delay)
  })
}

let flag = false
class SuperTask {
  constructor(cap) {
    this.cap = cap
    this.heap = new Heap((a, b) => a.priority - b.priority)
    this.runningCount = 0
  }

  addTask(task, priority = 0) {
    const p =  new Promise((res, rej) => {
      this.heap.push({
        task,
        res,
          rej,
          priority: priority
        })
      })
      if(!flag) {
        flag = true
        setTimeout(() => {
        //新增log
          console.log('执行了')
          this.__runtask()
          flag = false;
        }, 0)
      } 
      
      return p
    }

  __runtask() {
      while (this.runningCount < this.cap && this.heap.size()) {
        const {task, res, rej} = this.heap.pop();
        this.runningCount++;
        task().then(res).catch(rej).finally(() => {
          this.runningCount--;
          this.__runtask();
        })
      }
  }
}


const superTask = new SuperTask(2);
superTask.addTask(() => delay(2000), 4).then(() => console.log('Task 1 completed'));
superTask.addTask(() => delay(500), 5).then(() => console.log('Task 2 completed'));
superTask.addTask(() => delay(5000), 1).then(() => console.log('Task 3 completed'));

输出:

image.png 正确,log仅输入了一次

全部代码

import Heap from 'heap'

function delay(delay) {
  return new Promise(res => {
    setTimeout(res, delay)
  })
}

let flag = false
class SuperTask {
  constructor(cap) {
    this.cap = cap
    this.heap = new Heap((a, b) => a.priority - b.priority)
    this.runningCount = 0
  }

  addTask(task, priority = 0) {
    const p =  new Promise((res, rej) => {
      this.heap.push({
        task,
        res,
          rej,
          priority: priority
        })
      })
      if(!flag) {
        flag = true
        setTimeout(() => {
          this.__runtask()
          flag = false;
        }, 0)
      } 
      
      return p
    }

  __runtask() {
      while (this.runningCount < this.cap && this.heap.size()) {
        const {task, res, rej} = this.heap.pop();
        this.runningCount++;
        task().then(res).catch(rej).finally(() => {
          this.runningCount--;
          this.__runtask();
        })
      }
  }
}


const superTask = new SuperTask(2);
superTask.addTask(() => delay(2000), 4).then(() => console.log('Task 1 completed'));
superTask.addTask(() => delay(500), 5).then(() => console.log('Task 2 completed'));
superTask.addTask(() => delay(5000), 1).then(() => console.log('Task 3 completed'));