Promise 厨神争霸赛:async/await 爆炒并发!

45 阅读4分钟

promise是一个在面试中很常见的知识,这篇文章研究了手写实现一个promise,并实现Promise池并发,借此和各位读者一起重温/学习这个 JS 中的重要内容。

Proimse

实现promise ,Promise.race() ,Promise.allSettle() , Promise.all() ,promiseInstence.then()

分析原生Promise调用

const promise1 = new Promise((resovle,reject)=>{})
//作为一个类,传入一个回调函数,回调函数的参数是Promise的内部方法
p1.then(res=>{}).then().then().then()
//对实例调用then方法,传入一个回调函数,接受一个result|reason
//proimse实例的状态落定之后会执行then传入的回调函数

提出需求和对策

  1. promise类接受一个executor回调参数

    1. 接受两个回调,由Promise内部提供

  2. promise实例上有then方法

    1. then方法能够链式调用

    2. then方法中的回调函数在promise落定的时候被调用

    3. then方法执行后value时then中回调返回的方法(resolve)

  1. 构造函数接受一个executor参数

    1. 在constructor中定义reject和resolve,传入执函数
  2. promise类上挂载then方法,接受两个回调函数

    1. then方法返回一个promise,

    2. 这个promise根据上一个promise的状态执行回调或者未落定时推入任务队列,任务队列在原promise落定时被执行

    3. then方法返回的promise的executor中执行resolve(fullFilled(this.value)) || this.value)

export class myPromise {
  constructor(executor) {
    this.status = 'pending';
    this.value = undefined;
    this.reason = undefined;
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];

    let resolve = (value) => {
      if (this.status === 'pending') {
        this.status = 'resolved';
        this.value = value;
        this.onFulfilledCallbacks.forEach(fn => fn());
      }
    };

    let reject = (reason) => {
      if (this.status === 'pending') {
        this.status = 'rejected';
        this.reason = reason;
          this.onRejectedCallbacks.forEach(fn => fn());
      }
    };

    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err)
    }
  }

  then(onFulfilled, onRejected) {
    return new myPromise((resolve, reject) => {
      if (this.status === 'resolved') {
        try {
          let x = onFulfilled(this.value);
          resolve(x || this.value);
        } catch (e) {
          reject(e);
        }
      }
      if (this.status === 'rejected') {
        try {
          let x = onRejected(this.reason);
          resolve(x || this.reason);
        } catch (e) {
          reject(e);
        }
      }
      if (this.status === 'pending') {
        this.onFulfilledCallbacks.push(() => {
          try {
            let x = onFulfilled(this.value);
            resolve(x || this.value);
          } catch (e) {
            reject(e);
          }
        });
        this.onRejectedCallbacks.push(() => {
          try {
            let x = onRejected(this.reason);
            resolve(x || this.reason);
          } catch (e) {
            reject(e);
          }
        });
      }
    });
  }
}

Promise.prototype.finally

  • finally的回调没有参数,也就无法访问then方法的返回值
  • finally回调如果返回一个Promise,那么等待Promise成功,返回上一个Promise的值(成功或者失败)
  • 如果finally回调返回的Promise失败,那么返回的值是此Promise失败的值

原理是,finnally返回的是一个proimse,这个proimse只有的then方法只有成功回调,没有失败回调

finnally(callback){
    return this.then(
        (result )=>{return Promise.resolve(callback).then(()=>return result )},
        (reason )=>{return Promise.resolve(callbakc).then(()=>return reason )}
    )
}

Promise同步

  1. 使用链式调用 (then)
  2. 或者使用async await

Promise并发

  1. 使用类,类内部记录

    1. 当前执行的数量
    2. 已经完成的数量
    3. promiseList的长度
    4. promiseList的map(添加index用于保证result有序)
    5. result 保序的结果
  2. run函数 执行的主函数

    1. 判断现在能够执行多少个promise,从promiseList中拿出

    2. Promse执行完后的then添加结果到then

    3. 其finally将当前执行数 -- ,并触发run函数再次执行

      1. 当所有的请求都完成的时候,触发getResult函数的PromiseResolve将结果返回 Resolve(this.result)
  3. getResult函数将resolve暴露到全局,供finally触发,返回

下面是用类/函数两种 方法实现 Promise 并发

// 核心逻辑 1 调度器在promise落定的时候递归,计算当前能够执行的个数
// 核心逻辑 2 PromisePool的状态所有promise落定的时候改变,返回所有result组成的数组
class PromisePool {
        constructor(maxConcurrent, promiseList) {
                this.maxConcurrent = maxConcurrent
                this.promiseList = promiseList.map((item, index) => {
                        return { item, index }
                })
                this.promiseLength = promiseList.length
                this.runningCount = 0
                this.settleCount = 0
                this.allSettle = undefined
                this.result = []
                this.promise = new Promise(resolve => {
                        this.allSettle = resolve
                })
        }

        run() {
                const readyToRun = Math.min(
                        this.promiseList.length,
                        this.maxConcurrent - this.runningCount
                )
                for (let i = 0; i < readyToRun; i++) {
                        const target = this.promiseList.shift()
                        this.runningCount++
                        target
                                .item()
                                .then(res => {
                                        this.result[target.index] = res
                                })
                                .catch(e => {
                                        this.result[target.index] = e
                                })
                                .finally(() => {
                                        this.settleCount++
                                        this.runningCount--
                                        if (this.settleCount === this.promiseLength) {
                                                this.allSettle(this.result)
                                        } else {
                                                this.run()
                                        }
                                })
                }
                return this.promise
        }
}
function concurrentPromsie(maxConcurrent, promiseList) {
        const proimseLength = promiseList.length
        promiseList = promiseList.map((item, index) => {
                return { item, index }
        })
        let runningCount = 0
        let settleCount = 0
        let allsettle = null
        let result = []
        let promise = new Promise(resolve => {
                allsettle = resolve
        })

        function run() {
                const readyToRun = Math.min(promiseList.length, maxConcurrent - runningCount)
                for (let i = 0; i < readyToRun; i++) {
                        runningCount++
                        const target = promiseList.pop()
                        target
                                .item()
                                .then(res => {
                                        result[target.index] = res
                                })
                                .catch(e => {
                                        result[target.index] = e
                                })
                                .finally(() => {
                                        runningCount--
                                        settleCount++
                                        if (settleCount === proimseLength) {
                                                allsettle(result)
                                        } else {
                                                run()
                                        }
                                })
                }
                return promise
        }

        return run()
}

const func1 = async () =>
        new Promise(resolve => {
                setTimeout(() => {
                        console.log('func1')
                        resolve(1)
                }, 2000)
        })
const func2 = async () =>
        new Promise(resolve => {
                setTimeout(() => {
                        console.log('func2')
                        resolve(2)
                }, 1000)
        })

const func3 = async () =>
        new Promise(resolve => {
                setTimeout(() => {
                        console.log('func3')
                        resolve(3)
                }, 1000)
        })

const func4 = async () =>
        new Promise(resolve => {
                setTimeout(() => {
                        console.log('func4')
                        resolve(4)
                }, 1000)
        })

const func5 = async () =>
        new Promise(resolve => {
                setTimeout(() => {
                        console.log('func5')
                        resolve(5)
                }, 1000)
        })

const func6 = async () =>
        new Promise(resolve => {
                setTimeout(() => {
                        console.log('func6')
                        resolve(6)
                }, 1000)
        })

const func7 = async () =>
        new Promise(resolve => {
                setTimeout(() => {
                        console.log('func7')
                        resolve(7)
                }, 1000)
        })

const func8 = async () =>
        new Promise(resolve => {
                setTimeout(() => {
                        console.log('func8')
                        resolve(8)
                }, 1000)
        })

const func9 = async () =>
        new Promise(resolve => {
                setTimeout(() => {
                        console.log('func9')
                        resolve(9)
                }, 1000)
        })

const func10 = async () =>
        new Promise(resolve => {
                setTimeout(() => {
                        console.log('func10')
                        resolve(10)
                }, 1000)
        })

// const promisePool = new PromisePool(3, [
//         func1,
//         func2,
//         func3,
//         func4,
//         func5,
//         func6,
//         func7,
//         func8,
//         func9,
//         func10,
// ])

// promisePool.run().then(res => console.log(res))

concurrentPromsie(2, [
        func1,
        func2,
        func3,
        func4,
        func5,
        func6,
        func7,
        func8,
        func9,
        func10,
]).then(res => console.log(res))

笔者才疏学浅,各位读者多多担待,不吝赐教。