手写Promise-知其然知其所以然

594 阅读9分钟

前言: 对于一件东西最好的理解就是能够清楚知道他是如何实现的,正所谓知其然知其所以然,开始吧

1、关于promise的介绍

首先,我们知道,Promise是率先由commonJs提出来的一种异步解决方案,后来在ES6中正式引入,成为我们处理异步的强有力的工具。

我们通过自己手动的方式进行梳理一遍Promise是实现的过程,让我们清楚它的内如是如何是实现的

2、实现Promise的过程

1、promise 是一个类,在执行这个类的时候会传入一个执行器executor,该类一经创建会立即执行。

2、Promise 一共有三种状态,等待(pedding)、成功(fulfilled)、失败(rejected)。

  • 在成功状态的时候执行resolve 状态: pedding -> fulfilled

  • 在失败状态的时候执行reject 状态: pedding -> rejected

  • 这些状态一经改变不可更改

3、创建类时传入的执行器中会有两个函数参数,在执行该执行器的时候,这两函数会执行其一

4、在Promise类中有一个then方法,它是用来判断返回状态的,他回传入两个函数作为参数,successCallBack(成功回调函数)、failCallBack(失败回调函数)

  • successCallBack是执行了resolve函数后调用的函数,它的参数就是resolve的传入参数,是执行成功的结果
  • failCallBack 是执行力了reject函数后调用的函数,它的参数就是reject的传入参数,是执行失败的原因

下面用代码实现一下 (下面都是以MyPromise来展示)

代码1:

 // 标记类的三种执行状态
  const PADDING = 'pedding' // 等待
  const FULFILLED = 'fulfilled' // 成功
  const REJECTED = 'rejected' // 失败
  
  class MyPromise {
      // 构造函数
      constructor (executor) {
          executor(this.resolve, this.reject) // 传入的执行器
      }
      
// 用来记录该类当前的状态
      status = PADDING
      // 记录执行成功的结果
      result = undefined
      // 记录执行失败的原因
      reason = undefined
      // 保存处于等待状态的成功回调函数的结果集
      successCallBack = undefined
       // 保存处于等待状态的失败回调函数的原因集
      failCallBack = undefined
      // 执行成功调用的函数,不使用旧的function是关于this指向的当前Promise对象中的问题
     resolve = result => {
          if (this.status === FULFILLED) return
          this.status = FULFILLED
          this.result = result
          this.successCallBack && this.successCallBack(this.result)
     }
     // 执行失败调用的函数,
     reject = reason => {
          if (this.status === REJECTED) return
          this.status = REJECTED
          this.reason = reason
          this.failCallBack && this.failCallBack(this.reason)
     }
     // then方法用来执行回调函数
     then (successCallBack, failCallBack) {
        if (this.status === FULFILLED) {
            successCallBack(this.result)
        } else if (this.status === REJECTED) {
            failCallBack(this.reason)
        } else {
            // 这里将等待状态的回调函数保存起来
            this.successCallBack = successCallBack
            this.failCallBack = failCallBack
        }
     }
  }

这里就基本实现了上述陈述的几条步骤,但是还有一些功能要进行修改

5、当我们连续执行then方法的时候,对于异步处理结果而言,要将等待时候的成功回调函数或者失败回调存储起来(数组 - 多次调用then),在执行成功/失败后调用

下面将代码1中的一些地方进行改动

代码2:

       // 保存处于等待状态的成功回调函数的结果集
      successCallBack = undefined
       // 保存处于等待状态的失败回调函数的原因集
      failCallBack = undefined

为了能够多次同步调用then方法,我们将它改为数组,方便存储多个回调函数

代码3:

     // 保存处于等待状态的成功回调函数的结果集
      successCallBack = []
       // 保存处于等待状态的失败回调函数的原因集
      failCallBack = []

还有 then 方法 、resolve 和reject也要修改回调函数保存方式

代码4:

 // 执行成功调用的函数,不使用旧的function是关于this指向的问题
     resolve = result => {
          if (this.status === FULFILLED) return
          this.status = FULFILLED
          this.result = result
          // 这里判断数组长度,再向前依次推出回调函数去执行
          while (this.successCallBack.length) this.successCallBack.shift()(this.result)
     }
     // 执行失败调用的函数,
     reject = reason => {
          if (this.status === REJECTED) return
          this.status = REJECTED
          this.reason = reason
         // 当执行多次then失败时,通过数组调用的回调函数依次朝前推出执行回调函数
          while (this.failCallBack.length) this.failCallBack.shift()(this.reason)
     }
     
    then (successCallBack, failCallBack) {
            if (this.status === FULFILLED) {
                successCallBack(this.result)
            } else if (this.status === REJECTED) {
                failCallBack(this.reason)
            } else {
                // 这里将等待状态的回调函数保存起来
                this.successCallBack.push(successCallBack)
                this.failCallBack.push(failCallBack)
            }
         }

6、对于then方法的链式调用,可以通过返回一个Promise的实例来实现,在每个Promise中都有then方法,通过将返回值传递到下一个Promise中,这样我们就可以实现then方法的链式调用了,这里改变一下then方法的实现

在then方法中,每次返回一个新的Promise对象

代码5:

then (successCallBack, failCallBack) {
            let  newPromise = new MyPromise ( (resolve, reject) => {
             if (this.status === FULFILLED) {
                successCallBack(this.result)
            } else if (this.status === REJECTED) {
                failCallBack(this.reason)
            } else {
                // 这里将等待状态的回调函数保存起来
                this.successCallBack.push(successCallBack)
                this.failCallBack.push(failCallBack)
            }
            })
            return newPromise
         }

7、值得注意的是,对于上一个链式的回调函数,如果有返回值,要进行区分是普通值还是异步操作类(例如:Promise)的值,如果是普通值,直接返回, 如果是Promise之类的,等待它的结果进行成功或者失败的执行。这里还要对then方法进行进一步的升级。

代码6:

then (successCallBack, failCallBack) {
            let  newPromise = new MyPromise ( (resolve, reject) => {
             if (this.status === FULFILLED) {
                 // 获取成功回调的返回值
                const x = successCallBack(this.result)
                // checkePromise方法是用来判断上一个Promise返回的是普通值还是Promise值,并对其做进一步的处理
                checkeValue (x,resolve, reject)
            } else if (this.status === REJECTED) {
                // 获取失败回调的返回值
                const err = failCallBack(this.reason)
                // checkePromise方法是用来判断上一个Promise返回的是普通值还是Promise值,并对其做进一步的处理
                checkeValue (err,resolve, reject)
            } else {
                this.successCallBack.push(successCallBack)
                this.failCallBack.push(failCallBack)
            }
            })
            return newPromise
         }
         
         
         // 这个方法定义在MyPromise类之外
         function checkeValue (val,resolve, rejecct) {
             // 返回的是Promise对象
              if (val instanceof MyPromise) {
                  val.then( val => resolve(val), reason => {throw Error(reason)})
                  // val.then(resolve, rejecct )
              } else {
                    // 返回的是普通值
                      resolve(val)
              }
         }

8、还有,对于调用者写的回调函数,我们也要进行错误的抓取,在抛错的情况下进行reject失败返回(两处:执行器函数的执行,then的成功/失败回调),使用try catch 进行捕获。

代码7:

constructor (executor) {
          // 捕获代码的错误(执行器出)
          try {
            executor(this.resolve, this.reject)
          } catch (e) {
                this.reject(e)
          }
      }
      //  成功回调函数处, 将then方法中的成功回调函数处替换此代码
       try {
             const x = successCallBack(this.result)
             checkeValue (x,resolve, reject)
            } catch (e) {
              reject(e)
          }
          
          //  失败回调函数处, 将then方法中的失败回调函数处替换此代码
       try {
             const err = failCallBack(this.reason)
             checkeValue (err,resolve, reject)
            } catch (e) {
              reject(e)
          }

9、对于then函数的回调是否存在,如果存在,就执行,如果不存在,要将返回值或者失败原因向下一个Promise回调函数中传递,例如 new Promise.then().then().then(),在最后一个then中获取执行结果。

对于then方法返回的Promise对象中,为了判断自身调用自身的情况,使用了内部定义的变量(newPromise),但是对于异步操作来说,同步执行代码的情况下,无法立即获取未执行的异步结果,所有要将成功/失败回调函数的执行进行异步处理

then方法终于写完了!!!

代码8:

then (successCallBack, failCallBack) {
          // 这里对没有传入参数进行参数向下传递
        successCallBack = successCallBack ? successCallBack : val => val
        failCallBack = failCallBack ? failCallBack : err => { throw err }
          let newPromise = new MyPromise ((resolve, reject) => {
            if (this.status === FULFILLED) {
                setTimeout (() => {
                // 捕获成功回调函数的错误,返回给下一个Promise错误回调
                try {
                    const x = successCallBack(this.result)
                    // checkePromise方法是用来判断上一个Promise返回的是普通值还是Promise值,并对其做进一步的处理
                    // 为了防止自身调用自身的情况,需要添加一个对自身的判断  --> promise 和 val的判读
                    // 这里有一个问题:promise参数是当前结果返回所得,这里要加入异步操作来排队执行 --> 上面setTimeOut操作
                    checkeValue (newPromise,x,resolve, reject)
                } catch (e) {
                    reject(e)
                }
                }, 0)
            } else if (this.status === REJECTED) {
                setTimeout (() => {
                    try {
                        const err = failCallBack(this.reason)
                       checkeValue (newPromise,err,resolve, reject)
                    } catch (e) {
                        reject(e)
                    }
                    }, 0)
            } else {
                this.successCallBack.push(() => {
                    setTimeout (() => {
                        // 捕获成功回调函数的错误,返回给下一个Promise错误回调
                        try {
                            const x = successCallBack(this.result)
                            // checkePromise方法是用来判断上一个Promise返回的是普通值还是Promise值,并对其做进一步的处理
                            // 为了防止自身调用自身的情况,需要添加一个对自身的判断  --> promise 和 val的判读
                            // 这里有一个问题:promise参数是当前结果返回所得,这里要加入异步操作来排队执行 --> 上面setTimeOut操作
                            checkeValue (newPromise,x,resolve, reject)
                        } catch (e) {
                            reject(e)
                        }
                        }, 0)
                })
                this.failCallBack.push( () => {
                    setTimeout (() => {
                        try {
                            const err = failCallBack(this.reason)
                            checkeValue (promise,err,resolve, reject)
                        } catch (e) {
                            reject(e)
                        }
                        }, 0)
                })
            }
          })
          return newPromise
      }
      // -------------------------------------------------------------
      // 下面的checkeValue也添加了判断条件
       function checkeValue(promise,val,resolve, rejecct) {
      if ( promise === val) {
            throw TypeError('MyPromise into an endless loop')
      }
      // 返回的是Promise对象
      if (val instanceof MyPromise) {
          val.then( val => resolve(val), reason => {throw Error(reason)})
          // val.then(resolve, rejecct )
      } else {
        // 返回的是普通值
          resolve(val)
      }
       
}

3、这里关于Promise的基本功能就写完了,还有一些Promise 的常用方法下面进行补充

Promise中的静态方法 -- all 方法

代码9:

 /**
       * 思路:
       *    1、首先该方法会返回一个Promise对象,在成功对调中获取到一个结果数组
       *    2、要对数组中的每一个值进行处理,对于普通值,直接执行回调,
       *    对于Promise,则是先获取Promise结果,根据是否成功执行成功回调或者失败回调,再将回调值存储到结果数组中
       */
      static all(array) {
        let results = []
        let index = 0
        return new MyPromise ( (resolve, rejecct) => {
            function addArrData(i, data) {
                results[i] = data
                index++
                if (index === array.length) {
                    resolve(array)
                }
            }
            for (let i = 0; i < array.length; i++) {
                if (array[i] instanceof MyPromise) {
                    array[i].then( success => addArrData(i, success), error => rejecct(error))
                } else {
                    addArrData (i, array[i])
                }
            }
        })
      }

Promise中的静态方法 -- resolve 方法

代码10:

  /**
       * 思路:
       * 
       *  1、判断传入的值是否是Promise对象
       *    是: 直接将其返回出去
       *    否: new一个Promise对象将这个值传递进去,这样就返还一个Promise对象
       */
      static resolve (value) {
        if (value instanceof MyPromise) return value
        else return new MyPromise(resolve => resolve(value))
      }

Promise中的内置方法 -- finally (callBack)

代码11:

        /**
         * 思路:
         *  1、根据this.then  可以知道当前Promise 的返回结果,在成功和失败回调中都执行一下fifnally的回调函数
         *  2、这里会有一个问题:当对回调函数中有异步代码的时候,return会直接先返回,这里要处理一下这个情况
         *  3、可以使用Promise.resolve方法现将finally的回调函数扁平化处理,并且进行异步操作
         *  4、再根据返回值进行.then 的操作,进行成功回调和失败回调
         */
        finally (callBack) {
            this.then ( value => {
                // callBack()
                // return value
                return new MyPromise.resolve(callBack).then( () => value )
            }, reason => {
                // callBack()
                // throw Error(reason)
                return new MyPromise.resolve(callBack).then( () => { throw reason} )
            })
        }

Promise中的内置方法 --catch(failCallBack)

代码12:

        /**
         * 思路: 根据this.then 的方法返回值,获取执行结果,再直接让失败回调在this.then的失败回调中执行
         */
      catch (failCallBack) {
        this.then(undefined, failCallBack)
      }

4、Promise的结尾

关于Promise的实现基本的已经完成,还有一些的race,reject方法之类的后期会进行补充,通过手写一遍,我更清楚的知道了Promiose的使用和执行方式。


我是荆小乐,每两周都会更新一些我最近所学的知识和理解,希望能和你们共勉,如有不足,请指正,想知道更多内容,就请关注我,更多内容我们下期见!