前端性能优化:控制并发

458 阅读1分钟

前言

并发就是很多请求同时发送,当发送的请求过多时,就会造成服务器过载,网络拥堵等问题,大量请求同时通过网络传输,可能导致网络带宽饱和,从而影响所有请求的响应时间。

控制并发就是控制同时发送请求的数量,比如有一个页面里面有100个请求同时发送,如何控制其每次只能发送着5个请求。如何实现呢?

控制并发的代码实现

设计思路:假设有十个并发任务,控制并发数为2,维护一个任务队列,将这十个任务添加进这个给队列里面,然后取两个任务出队列并执行,当其中一个执行完后再取一个任务出队列接上去执行。

使用 ajax 函数模拟发送请求任务,然后再用 addTask 函数接受任务执行时间和任务名称,将任务(通过 ajax 函数模拟的异步操作)添加到 Limit 类的队列中,在任务完成后,执行回调函数打印任务完成的消息。

function ajax(time) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve()
    }, time)
  })
}

// ajax(1000)
// ajax(2000)
// ...

class Limit {
  constructor() {

  }
  add() {

  }
}

const limit = new Limit()

function addTask(time, name) {
  limit
    .add(() => ajax(time))  // 放入队列但不执行
    .then(() => {
      console.log(`任务${name}完成`);  // 为了更好地看出来执行效果
    })
}

addTask(10000, 1)
addTask(2000, 2)
addTask(5000, 3)
addTask(1000, 4)
addTask(7000, 5)

这样,就可以通过调用 addTask 函数来添加多个任务,然后在 Limit 类中实现的并发控制逻辑来执行。

然后在 Limit 类中维护一个任务队列,并接受并发数量。add 函数将该任务添加到任务队列,然后执行函数 _run,函数 _run 里面先判断当前正在执行的任务数量是否小于最大并发数,如果小于,也就是还没有达到并发数量,就可以取出队列里面的第一个任务来执行了,当其中一个执行完后就接着去任务执行,再调用 _run 函数。代码如下:

class Limit {
  constructor(paralleCount = 2) {
    this.runningCount = 0    // 正在执行的任务数量
    this.tasks = []     // 任务队列
    this.paralleCount = paralleCount  // 存储传入的最大并发数
  }
  add(task) {
    this.tasks.push(task)

    this._run()
  }

  _run() {
    // 是否达到并发数量
    while (this.runningCount < this.paralleCount && this.tasks.length) {
      const task = this.tasks.shift()   // 将任务队列里面的第一个任务取出来
      this.runningCount++

      task().then(() => {   // 执行任务
        this.runningCount--   // 执行完后正在执行的任务数量 -1
        this._run()    // 递归执行
      })
    }
  }
}

const limit = new Limit(2)

接下来就是考虑 Promise 里面的 resolve 和 reject 函数的调用了,我们可以在任务添加到任务队列的时候将 resolve 和 reject 与task任务一起添加进任务队列里面。这样当执行完一个任务时,就可以返回resolve或reject了。如下:

class Limit {
  constructor(paralleCount = 2) {
    this.runningCount = 0    // 正在执行的任务数量
    this.tasks = []     // 任务队列
    this.paralleCount = paralleCount  // 存储传入的最大并发数
  }
  add(task) {
    return new Promise((resolve, reject) => {
      // 将resolve, reject跟着task任务一起添加进队列中
      this.tasks.push({
        task,
        resolve,
        reject
      })

      this._run()
    })
  }

  _run() {
    while (this.runningCount < this.paralleCount && this.tasks.length) {
     // 将任务队列里面的第一个任务取出来,并将 resolve, reject解构出来
      const { task, resolve, reject } = this.tasks.shift()  
      this.runningCount++

      task()
        .then(() => {   // 执行
          resolve()
          this.runningCount--   // 执行完后正在执行的任务数量 -1
          this._run()    // 递归执行
        })
        .catch(() => {
          reject()
          this.runningCount--
          this._run()
        })
    }
  }
}

const limit = new Limit(2)

其中需要注意的就是:

  1. 保证所有的任务都受控制(给任务包裹一层函数)
  2. 创建添加任务的 add 函数,并将 add 的 resolve,reject 同任务一起存放
  3. 创建执行任务的 run 函数,在每个任务执行完毕后执行 add 函数的 reslove,reject,最后递归执行 run 函数

完整代码:

function ajax(time) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve()
    }, time)
  })
}

// ajax(1000)
// ajax(2000)
// ...

class Limit {
  constructor(paralleCount = 2) {
    this.runningCount = 0    // 正在执行的任务数量
    this.tasks = []     // 任务队列
    this.paralleCount = paralleCount  // 存储传入的最大并发数
  }
  add(task) {
    return new Promise((resolve, reject) => {
      this.tasks.push({
        task,
        resolve,
        reject
      })

      this._run()
    })
  }

  _run() {
    while (this.runningCount < this.paralleCount && this.tasks.length) {
      const { task, resolve, reject } = this.tasks.shift()   // 将任务队列里面的第一个任务取出来
      this.runningCount++

      task()
        .then(() => {   // 执行
          resolve()
          this.runningCount--   // 执行完后正在执行的任务数量 -1
          this._run()    // 递归执行
        })
        .catch(() => {
          reject()
          this.runningCount--
          this._run()
        })
    }
  }
}

const limit = new Limit(2)

function addTask(time, name) {
  limit
    .add(() => ajax(time))
    .then(() => {
      console.log(`任务${name}完成`);
    })
}

addTask(10000, 1)
addTask(2000, 2)
addTask(5000, 3)
addTask(1000, 4)
addTask(7000, 5)