面试官: 你会手写实现PromiseA+么? 小白的我: 这......, 要不我试试(系列一

197 阅读6分钟

问题/需求:

  • 手写Promise A+规范. 啥是A+其实我也不是很懂, 好像就是平时用的那一种? 不管了, 先上了再说.

解决/处理步骤:

分析过程:

  • Promise, 先得想清楚, 这玩意儿能干啥, 可以干啥, 才能反向推出, 我们该咋写.
  • Promise有可以传递参数, 参数一个, 而且是个函数, 准确的说是两个函数, 而且是钩子函数(hook 就是resolvereject, 我们平时在使用的时候, 如果内部处理完成符合我们的预期就resolve, 不符合或者报错就使用reject.
  • promise好像还有Promise.resolve(), Promise.reject(), Promise.all(), Promise.race()这些静态方法. 不管, 先一步一步来.
  • 先最简单, 也是最基础的, 就是Promise其实管理着三种方法, 在没有resolvereject之前, 是pending状态, 也就是等待状态, 在resolve之后, pending=> fulfilled, 或者在reject之后, pending => rejected状态, 并且, 一旦状态发生改变, 就没办法再次改变了.

初步实现

  • 既然理清了Promise的基本使用和其有的方法, 那么, 我们就开始从最简单的Promise开始撸.
  • 这里, 将使用class来定义, 其实也可以使用函数和Prototype原型来实现.
    class Promise{
    // 构造函数, 里面是一个执行器, 执行器会在类被实例化的时候, 自动调用
        constructor(exector){
        // 这里的resolve,reject就是我们在Promise((resolve, reject))的两个方法
            exector(this.resolve, this.reject)
        }
        // 定义resolve类方法
        resolve(){}
        // 定义reject类方法
        reject(){}
    }
  • 上面, 实现了一个最简单的Promise,可以实现, 用户传入resolvereject方法并进行执行, 但是! 其实里面是有的! 上面的exector执行器中, 执行的两个方法在node环境下, 是可以正常执行的, 但是, 在window浏览器环境下会报错, 因为, 当前window中并没有resolvereject, 所以, 就需要将resolvereject bind到当前的this上, 如下:
    exector(this.resolve.bind(this), this.reject.bind(this))
    

加入状态管理

  • 但是你会发现, 如果你在Promise中调用了resolve和reject方法, 她会都进行调用, 这和我们平时用到的Promise一旦resolve,或者reject后, 就无法改变状态不一致, 对了! 这里就得加入状态管理.

 class Promise{
    // 定义三个Promise内部的三种状态: 等待、完成、拒绝
    static PENDING = 'pending'
    static FULFILLED = 'fulfilled'
    static REJECTED = 'rejected'
    // 构造函数, 里面是一个执行器, 执行器会在类被实例化的时候, 自动调用
        constructor(exector){
        // 初始化当前的状态
        this.staus = Promise.PENDING
        // 管理处理后的数据
        this.value = null
        // 这里的resolve,reject就是我们在Promise((resolve, reject))的两个方法
            exector(this.resolve, this.reject)
        }
        // 定义resolve类方法
        resolve(value){
            // 判断当前是否是pending状态, 是=> 执行, 否=> 不做操作
            if(this.status=== Promise.PENDING){
                  // 保存一下需要resolve的数据 
                  this.value = value
                  // 状态改成fulfilled, 不让再次改变
                  this.status = Promise.FULFILLED
            }
        }
        // 定义reject类方法
        reject(reason){
             // 判断当前是否是pending状态, 是=> 执行, 否=> 不做操作
            if(this.status=== Promise.PENDING){
                  // 保存一下需要reject的失败的理由, 其实也可以是value, 不过希望可以顾名思义
                  this.value = reason
                  // 状态改成fulfilled, 不让再次改变
                  this.status = Promise.REJECTED
            }
        }
    }
  • 好, 到这一步, 既实现了功能, 又有状态管理, 不会被多次执行了. 但是, 其中还有问题, 如果, 用户在resolve或者reject中出现了异常咋办呢? 简单, 加上try{}catch{}不就好了! 好, 那咱们来加上!

 class Promise{
    // 定义三个Promise内部的三种状态: 等待、完成、拒绝
    static PENDING = 'pending'
    static FULFILLED = 'fulfilled'
    static REJECTED = 'rejected'
    // 构造函数, 里面是一个执行器, 执行器会在类被实例化的时候, 自动调用
        constructor(exector){
        // 初始化当前的状态
        this.staus = Promise.PENDING
        // 管理处理后的数据
        this.value = null
+        try{
+         // 这里的resolve,reject就是我们在Promise((resolve, reject))的两个方法
+           exector(this.resolve, this.reject)
+         }catch (error){
+          this.reject(error)
+         }
+      }
        // 定义resolve类方法
        resolve(value){
            // 判断当前是否是pending状态, 是=> 执行, 否=> 不做操作
            if(this.status=== Promise.PENDING){
                  // 保存一下需要resolve的数据 
                  this.value = value
                  // 状态改成fulfilled, 不让再次改变
                  this.status = Promise.FULFILLED
            }
        }
        // 定义reject类方法
        reject(reason){
             // 判断当前是否是pending状态, 是=> 执行, 否=> 不做操作
            if(this.status=== Promise.PENDING){
                  // 保存一下需要reject的失败的理由, 其实也可以是value, 不过希望可以顾名思义
                  this.value = reason
                  // 状态改成fulfilled, 不让再次改变
                  this.status = Promise.REJECTED
            }
        }
    }
  • constructor中的exector外面包一层trycatch, 这样出现了异常会reject出来.
  • 好了, 到目前为止, 确实是实现了resolvereject并且, 状态改变后, 不会再进行二次改变了. 现在来问一个问题, 使用Promise是用来解决什么, 或者Promise是为啥出现的, 你一定知道答案! 对! 答案就是为了解决回调地狱问题, 所以, Promise是可以链式执行的.使用的就是then()方法, 所以, 下面我们就来实现then()方法

then链式执行

  • then方法一样, 里面两个钩子方法, 一个是onfulfilledonrejected, 分别和上面的resolvereject对应, 如果上面是使用了resolve, 那么then中的onfulfilled方法就会被执行, 返回resolve中的data, 如果上面使用了reject, then中的onrejected方法就会被执行.
  • 下面就来实现一下then方法, 这里就只写then部分的代码了, 防止代码太多, 没有重点.

then(onfulfilled, onrejected){
    // 如果onfulfilled不是一个函数, 就不做操作
    if(typeof onfulfilled !== 'function'){ onfulfilled = () => {}}
    // 同理onrejected也是
    if(typeof onrejected !== 'function'){ onrejected = () => {}}
    
    // 因为是链式回调, 所以, then里面返回的也应该是一个Promise
    return new Promise((resolve, reject)=>{
        // 如果状态是fulfilled,那么就是执行了resolve方法
        if(this.status === Promise.FULFILLED){
             // 这里需要使用setTimeout将onfulfilled方法放入宏任务队列中
            setTimeout(()=>{
                try{
                    onfulfilled(this.value)
                }catch(err){
                    onrejected(err)
                }
            },0)
        }
        
        if(this.status ==== promise.REJECTED){
        // 同理将onrejected方法放入宏任务队列中
        setTimeout(()=>{
            try{
                onrejected(this.value)
            }catch(err){
                onrejected(err)
                }
            })
        }
        
        // 最重要的来了, 当状态是pending的时候
        if(this.status === promise.PENDING){
            // 1. 得在类中声明一个callbacks回调数组, 放置为被立马执行的方法
            // 2. 然后,将没有执行的方法, push到这个回调数组之中
            this.callbacks.push({
                onfulfilled:value=>{
                    try{
                     onfulfilled(value)
                    }catch(error){
                    onrejected(error)
                    }
                },
                
                onrejected:reason=>{
                    try{
                     onrejected(value)
                    }catch(error){
                    onrejected(error)
                    }
                }
            })
        
        }
    
    })

}

  • 写到这一步, 基本就把then方法中的几种情况搞定了, 但是! 这里应该有人会看出来, 我们把没有立即执行的方法push到了回调数组中, 那不能放在那不管了吧! 所以, 这里其实, 在上一步的resolvereject中, 应该做一些操作, 具体操作如下:
 resolve(value){
    if(this.status === NewP.PENDING){
      this.status = NewP.FULFILLED
      this.value  = value
       // 将方法改为宏任务, 实现异步调用
+      setTimeout(() => {
+       this.callbacks.map(callback => {
+        callback.onfulfilled(this.value)
+       })
+     })
    }
  }


  reject(reason){
    if(this.status=== NewP.PENDING){
      this.status = NewP.REJECT
      this.value = reason
    // 将方法改为宏任务, 实现异步调用
+      setTimeout(() => {
+       this.callbacks.map(callback => {
+        callback.onrejected(this.value)
+       })
+     });
    }
  }
  • 其实就是在当前的方法执行完了以后,去看看回调方法数组中, 有没有没被执行完的, 如果有, 就得去执行, 但是! 这里注意了, 执行回调函数数组中的方法, 应该是异步的, 所以,这里我们给加上了setTimeout定时器.

好了, 到目前, 我们已经实现了reslove, reject, then方法. all、race方法, 将下一个文章输出. 前端小白, 有错误和不当之处, 忘各位大神指出, 如果感觉小弟码字不易, 麻烦给个👍, 谢谢!