JavaScript实现Promise并发池 | PromisePool

1,342 阅读2分钟

什么是Promise并发池

Promise并发池的作用是当有大量Promise需要执行时,可以确保同时执行的Promise数量不超过设置的最大并发数,并且可以在某个Promise状态改变后自动执行新的Promise,即维持并发数始终为最大值(除去开始和结束阶段)。

怎样实现PromisePool

近日在面试过程中被多次问到该问题,经过几次修改最终得到以下版本,直接上代码,欢迎大家交流讨论。为了实现业务的解耦,在PromisePool类的定义中不应该包含对任何Promise业务逻辑的具体实现,如设置定时器,发送Ajax请求等,所有待执行的Promise应当作为一个函数的返回值在PromisePool的外部定义,然后将这些函数作为PromisePool每次需要处理的item。

class PromisePool {
    constructor(max) {
        this.max = max // 最大并发数
        this.num = 0 // 当前并发数
        this.index = 0 // 用于保证result中的顺序与taskList中的顺序相同
        this.result = [] // 保存所有Promise的结果
        this.taskList = [] // Promise列表
    }
    // 单例模式实现,在taskList中的全部任务完成之前,多次调用start会返回同一个Promise
    static result = null
    // 添加任务,每个任务是一个返回Promise的函数,或者是由此类函数组成的数组
    addTask(item) {
        if (Array.isArray(item)) this.taskList.concat(item)
        else this.taskList.push(item)
    }
    // 开始执行任务
    start() {
        // 如果PromisePool.result有值,则直接返回该值即可
        if (!PromisePool.result) {
            PromisePool.result = new Promise(resolve => {
                // 使并发数达到最大值
                while(this.num < this.max && this.taskList.length) {
                    let item = this.taskList.shift()
                    this.setTask(item, this.index, resolve)
                    this.num++
                }
            })
        }
        // 返回一个Promise,便于使用then()方法获取所有taskList中所有Promise的结果
        return PromisePool.result
    }
    // 执行Promise
    setTask(item, index, resolve) {
        this.index++
        // Promise成功或失败的结果均以对象的形式存储到result中,类似Promise.allSettled方法
        item().then(data => {
            this.result[index] = {state: 'fulfilled', data}
        }, error => {
            this.result[index] = {state: 'rejected', error}
        }).then(() => {
            this.num--
            // 如果taskList中还有任务,则取出第一项重复该过程
            if (this.taskList.length) {
                let newItem = this.taskList.shift()
                this.setTask(newItem, this.index. resolve)
                this.num++
            }
            // 如果并发数为0,表示所有任务完成,调用resolve方法并重置各项属性
            if (this.num === 0) {
                resolve(this.result)
                PromisePool.result = null
                this.index = 0
                this.result = []
            }
        })
    }
}
// 使用案例,每一个item都是一个返回Promise的函数
let item1 = function () {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log(1)
      resolve(1)
    }, 1000)
  })
}
let item2 = function () {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log(2)
      resolve(2)
    }, 2000)
  })
}
let item3 = function () {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log(3)
      resolve(3)
    }, 2000)
  })
}
let item4 = function () {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log(4)
      resolve(4)
    }, 3000)
  })
}
let item5 = function () {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log(5)
      resolve(5)
    }, 4000)
  })
}
let item6 = function () {
  console.log(6)
  return Promise.resolve(6)
}
let item7 = function () {
  console.log(7)
  return Promise.reject(7)
}
const promisePool = new PromisePool(2)
promisePool.addTask(item1)
promisePool.addTask(item2)
promisePool.addTask(item3)
promisePool.addTask(item4)
promisePool.addTask(item5)
promisePool.addTask(item6)
promisePool.addTask(item7)
promisePool.start().then(data => {
    console.log(data)
})
// 多次调用start方法返回同一个promise
promisePool.start().then(data => {
    console.log(data)
})
promisePool.start().then(data => {
    console.log(data)
})
// 会依次打印 1 2 3 4 6 7 5,然后打印三次相同的数组(长度为7)

首次在掘金发文,若有不当之处,恳请各位大佬指正。