Promise学习 - Promises/A+规范实现MyPromise

99 阅读7分钟

手写Promise

根据Promises/A+规范实现Promise

定义初始结构

Promise的使用是通过 new来实例化一个Promise对象,并且传入一个函数

  const P = new Promise((resolve:any,reject:any) => {

  })

所以我们需要先定义Promise类,并在类中定义一个构造函数,在构造函数中执行传入的函数,并且执行的函数还有两个参数resolve和reject

  export class MyPromise {

    constructor(executor:Function){
      executor(resolve,reject)
    }
  }

实现Promise状态的改变

Promise的状态分为 pending(等待),fulfiiled(成功),rejected(失败),所以我们也要先把这三个状态准备一下,这边可以定义3个常量方便后续的维护

  protected static PENDING = 'pending'

  protected static FULFILLED = 'fulfilled'

  protected static REJECTED = 'rejected'

实现resolve,和reject来实现状态的改变

我们需要准备好变量promiseState和promiseResult

promiseState来保存Promise的状态改变,并在构造函数中给他初始化,初始化状态为pending` promiseResult来保存resolve和reject执行是传入的参数,并在构造函数中给他初始化,初始化值为null

  constructor(executor:Function){
    this.promiseState = PromiseBase.PENDING
    this.promiseResult = null
    executor(resolve,reject)
  }

  public promiseState:string

  public promiseResult:unknown

实现resolve和reject函数,来改变状态

由于promise的状态只改变一次,所以加上对应的判断,只有在pending状态下才进行改变

  public resolve(result:unknown): void {
      if(this.promiseState === MyPromise.PENDING){
        this.promiseState = MyPromise.FULFILLED
        this.promiseResult = result
      }
  }

  public reject(reason:unknown): void {
    if(this.promiseState === MyPromise.PENDING){
      this.promiseState = MyPromise.REJECTED
      this.promiseResult = reason
    }
  }

有了resolve和reject函数,构造函数中的执行器参数就可以调用这两个函数来进行初始化

constructor(executor:Function){
    this.promiseState = PromiseBase.PENDING
    this.promiseResult = null
    // 执行器为外部传入函数,在实例化回调函数的this指向不指向类本身,需要用bind改变this指向
    executor(this.resolve.bind(this),this.reject.bind(this)) 
  }

实现then方法

Promise的then方法接收2个回调函数,onFulfilled和onRejected,并且返回一个新的Promise

onFulfilled会在状态为fulfiied时执行,onRejected会在状态为rejected时执行

  const P = new Promise((resolve:any,reject:any) => {
    resolve('1')
  })
  p.then(
    // onFulfilled
    (result:any) => {
      // onFulfilled回调会携带resolve执行的参数
      console.log(result) //输出'1'
    },
    // onRejected
    (reason:any) => {
      //初始化时调用的是resolve函数,所以状态为fulfiiled,所以onRejected不会执行
      console.log(reason) 
    }
  )
  const P1 = new Promise((resolve:any,reject:any) => {
    reject(new Error ('1'))
  })
    p.then(
    // onFulfilled
    (result:any) => {
      //初始化时调用的是reject函数,所以状态为rejected,所以onFulfilled不会执行
      console.log(result) 
    },
    // onRejected
    (reason:any) => {
      // onRejected回调会携带reject执行的参数
      console.log(reason) // 输出 Eroor 1
    }
  )

综上可实现我们的then方法

   public then(onFulfilled?:any,onRejected?:any){
    const _promise = const _promise = new MyPromise((resolve:any,reject:any) => {
       if(this.promiseState === MyPromise.FULFILLED){
        onFulfilled(this.promiseResult)
       }
       if(this.promiseState === MyPromise.REJECTED){
        onRejected(this.promiseResult)
       }
    }
   }

异常校验和错误捕获

我们知道Promise是无法被try/catch的

  try{
    const P = new Promise((resolve:any,reject:any) => {
      new Error('错啦错啦')
    })
  }catch(e){
    // 无法捕获
    console.log(e)
  }

那是因为在内部构造函数中已经把错误捕获了,如果发生异常则直接执行reject,所以我们来改造一下构造函数

  constructor(executor:Function){
    this.promiseState = PromiseBase.PENDING
    this.promiseResult = null
    try{
      // 执行器为外部传入函数,在实例化回调函数的this指向不指向类本身,需要用bind改变this指向
      executor(this.resolve.bind(this),this.reject.bind(this)) 
    }catch(error){
      // 发生错误时捕获错误
      // 这边不用bind来改变this指向是因为这边是立即执行而不是实例化后再执行,所以this指向不会发生改变
      this.reject(error)
    }
  } 

同时Promise.then接收到的2个参数并不一定是一个函数

  const P = new Promise((resolve:any,reject:any) => {
      reject('1')
  })
  p.then(
    undefined,
    (reason:any) => {
      console.log(reason) // 输出'1'
    }
  )

所以我们的then方法要加上参数校验,不是函数直接返回

  public then(onFulfilled?:any,onRejected?:any){
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : reason => {
      throw reason;
    };
    const _promise = const _promise = new MyPromise((resolve:any,reject:any) => {
       if(this.promiseState === MyPromise.FULFILLED){
        onFulfilled(this.promiseResult)
       }
       if(this.promiseState === MyPromise.REJECTED){
        onRejected(this.promiseResult)
       }
    }
   }

实现异步

Promise的then函数中的回调是异步执行的,并且会放进微队列中以微任务执行,这边常常会被出面试题,来考察事件循环的掌握程度

面试题: 说出以下执行顺序

  console.log('1')
  setTimeout(() => {
    console.log('2')
  },0)
  const P = new Promise((resolve:any,reject:any) => {
    resolve('3')
  })
  P.then(
    (result:any) => {
      console.log(result)
      console.log('4')
      setTimeout(() => {
        console.log('6')
      })
    }
  )
  console.log('5')
  // 输入顺序 1 5 3 4 2 6

实现then的回调函数异步执行

根据Promises/A+ 规范,回调函数可以放在宏队列也可以放在微队列,所以我们直接用SetTimeout将回调函数包一下,这边为了后续的拓展,封装了一个方法去将回调函数异步,另外再封装一个函数用来判断参数是否为函数

  static taskMircoQueue(callback:any){
    // TODO 暂时以setTimeout为准
    // 后面补充node环境微任务nextTick及浏览器环境微任务MutationObserver
    return setTimeout(callback,0)
  }

  static isFunc(func:any){
    const result = typeof func === 'function' ? true : false
    return result
  }

改造后的then方法

  public then(onFulfilled?:any,onRejected?:any){
    const _promise = const _promise = new MyPromise((resolve:any,reject:any) => {
       if(this.promiseState === MyPromise.FULFILLED){
        taskMircoQueue(() => {
          if(!isFunc(onFulfilled)){
            resolve(this.promiseResult)
          }else{
            onFulfilled(this.promiseResult)
          }
        })
       }
       if(this.promiseState === MyPromise.REJECTED){
         taskMircoQueue(() => {
            if(!isFunc(onRejected)){
              reject(this.promiseResult)
            }else{
              onRejected(this.promiseResult)
            }
         })
       }
    }
   }

处理pending状态下的回调

then方法中如果又遇到异步执行后才改变状态时,我们就需要将回调函数先进行保存,后续再进行输出

  // 根据事件循环的执行机制,渲染主线程会优先执行微队列中的任务,所以会先执行then,而Promise的状态此时还没有改变,所以此时的状态为pending
  const P = new Promise((resolve:any,reject:any) => {
      // setTimeout是宏任务
      setTimeout(() => {
        resolve('1')
      },0)
  })
  // Promise.then是微任务
  P.then((result:any) =>{
    console.log(resolve)
  })

所以我们需要在then方法中增加pending的逻辑判断,将pending时的回调存起来,然后在resolve和reject函数中去调用,由于Promise.then还具有链式调用,所以我们需要用数组来存储回调

  /**
   * 成功回调函数集合
   */
  public onFulfilledCallBacks:Array<any>

  /**
   * 失败回调函数集合
   */
  public onRejectCallBacks:Array<any>

改造后的resolve和reject

  public resolve(result:unknown): void {
      if(this.promiseState === MyPromise.PENDING){
        this.promiseState = MyPromise.FULFILLED
        this.promiseResult = result
        this.onFulfilledCallBacks.forEach((callback:Function) => {
          callback(result)
        })
      }
  }

  public reject(reason:unknown): void {
    if(this.promiseState === MyPromise.PENDING){
      this.promiseState = MyPromise.REJECTED
      this.promiseResult = reason
      this.onRejectCallBacks.forEach((callback:Function) => {
        callback(reason)
      })
    }
  }

改造后的then

  public then(onFulfilled?:any,onRejected?:any){
    const _promise = const _promise = new MyPromise((resolve:any,reject:any) => {
      if(this.promiseState === MyPromise.PENDING){
        this.onFulfilledCallBacks.push(() =>{
          taskMircoQueue(() => {
            if(!isFunc(onFulfilled)){
              resolve(this.promiseResult)
            }else{
              onFulfilled(this.promiseResult)
            }
          })
        })
        this.onRejectCallBacks.push(() => {
          taskMircoQueue(() => {
            if(!isFunc(onRejected)){
              reject(this.promiseResult)
            }else{
              onRejected(this.promiseResult)
            }
         })
        })
      }
       if(this.promiseState === MyPromise.FULFILLED){
        taskMircoQueue(() => {
          if(!isFunc(onFulfilled)){
            resolve(this.promiseResult)
          }else{
            onFulfilled(this.promiseResult)
          }
        })
       }
       if(this.promiseState === MyPromise.REJECTED){
         taskMircoQueue(() => {
            if(!isFunc(onRejected)){
              reject(this.promiseResult)
            }else{
              onRejected(this.promiseResult)
            }
         })
       }
    }
  }

then方法的链式调用

这边根据Promises/A+规范,链式调用需满足以下几点

  • 1.then方法返回一个新的Promise
  • 2.不论Promsie被reject或resolve,都会执行Promise解决过程

所以我们需要写一个Promise解决过程的函数resolvePromise,通过resolvePromise对resolve和reject进行增强

resolvePromise需要处理以下几种情况

  • 1.promise与x指向对一对象,则拒绝执行(防止循环引用)
  • 2.如果x的返回值为Prmose,则使新的promise接收x的状态
  • 3.如果resolve和reject均被调用或者被同一参数调用多次,则优先采用首次调用并忽略剩下的调用
  • 4.如果x是thenable对象,并且x.then是函数,则将x作为函数的作用域调用resolve或reject
  • 5.如果x是thenable对象,并且x.then不是函数,以x作为参数执行Promise
    /**
   * Promises/A+规范 promise解决过程
   * 针对resolve和reject的不同值进行处理
   * @param promise then方法返回的新的promise
   * @param x then方法中onFulfiied或onReject的返回值
   * @param resolve 新promise的resolve方法
   * @param reject 新promise的reject方法
   */
  static resolvePromise(promise:any,x:any,resolve:any,reject:any){
    // Promises/A+规范 如果promise和x指向同一对象,以typeError为据因拒绝执行promise
    // 如果从onFulfilled或onReject中返回的x就是新promise,则会导致循环引用问题,这部分就是处理循环引用问题导致的报错
    if(x === promise){
      throw new TypeError("chaining cycle detected for promise")
    }
    if(x instanceof MyPromise){
      // Promises/A+规范 如果x为promise,则使新的promise接受x的状态
      x.then((_x:any) => {
        this.resolvePromise(promise,_x,resolve,reject)
      })
    }
    if(x!=null && ((typeof x === 'object' || (typeof x === 'function')))){
      let then
      try{
        // 把x.then赋值为then
        then = x.then
      }catch(e){
        // 如果x.then的值抛出异常错误e,则以e为拒因拒绝promise
        return reject(e)
      }

      if(typeof then === 'function'){
        // 如果resolve和reject均被调用或者被同一参数调用多次,则优先采用首次调用并忽略剩下的调用
        let called:boolean = false
        try{
          then.call(
            x,
            // 如果resolve以值y为参数被调用,则运行[[Resolve]](promise,y)
            (y:any) => {
              if(called) return
              called = true
              this.resolvePromise(promise,y,resolve,reject)
            },
            // 如果reject以据因r为参数被调用,则以据因r拒绝promise
            (r:any) => {
              if(called) return
              called = true
              reject(r)
            }
          )
        }catch(e){
          if(called) return
          called = true
          reject(e)
        }
      }else{
        // 如果then不为函数,以x为参数执行promise
        resolve(x)
      }
    }else{
      // 如果x不为函数或对象,以x为参数执行promise
      return resolve(x)
    }
  }

参考文章:juejin.cn/post/704375…