还不能手写 Promise?(请务必看完)

87 阅读6分钟

我正在参与掘金创作者训练营第5期,点击了解活动详情

简述

  • 面试官: 讲一下对 promise 的理解吧

    • 一脸兴奋:我会手写!
  • 面试官: 那写吧

    • 打开 vsCode emmm 那个那个 这有点忘了
  • 面试官:.....那就这样吧 回去等通知

  • yysy 手写 promise 是挺难的 如果流程不太清楚的话 很有可能会卡住 不过手写都不容易

  • 下面还是介绍一下整个 从零到一的思路吧 理解清除还是能去碰一碰的

    • 会手写就有底气嘛 哈哈哈

分析

  • 要手写的先知道 Promise 有哪些功能 然后才能根据相应的功能完成代码

    • 可以结合自己平时写得以及测试 来看看 Promise 有哪些功能嘛

    • 1.三个状态 状态不可能

    • 2.有两个回调 resolve reject

    • 3.异步执行

    • 4..then()

      • 返回一个 Promise 对象
      • 可以传递两个回调 也可以不穿参数
      • 可以链式调用 值传递 值穿透 直到状态改变才返回结果
    • 5..catch()

      • 返回失败结果
    • 6..all()

      • 全部成功才成功
    • 7..race()

      • 返回最先改变状态得结果
  • 首先还是得先写构造函数 之后再为函数添加方法

  • 下面会按照顺序进行编写

测试代码

  • 可以控制 <script src="./promise.js"></script> 得引入和原生得 Promise 进行对比
  • <script src="./promise.js"></script>
    <script>
      // test
      console.log('同步任务 111')
      let p = new Promise((resolve, reject) => {
        resolve('ok')
        // reject('error')
        // setTimeout(() => {
        //   resolve('ok')
        //   // reject('error')
        // }, 500);
      })
    ​
      // console.log(p, '同步任务 p')
      let res = p.then(value => {
        console.log('异步任务 111') 
        return value
      }, reason => {
        return reason
      })
      res.then(value => {
        console.log(value, '异步任务 222')
        return value
      }, reason => {
        return reason
      }).then().catch(err => {
        console.log(err)
      })
    ​
      console.log(res, '同步任务 res')
      console.log('同步任务 222')
    ​
      let p1 = new Promise(resolve => resolve('p1'))
      let p2 = new Promise(resolve => resolve('p2'))
    ​
      /* 
      + 这里要注意一点 
        + 要写成 Promise.prototype.all()
        + 因为我定义在原型链上面
          + 如果想直接 Promise.all() 那就直接定义在 Promise 构造函数上面就行了
        + 不对啊? 那为什么上面可以使用 .then
          + 因为那是实例对象 p 上使用 他会通过 隐式原型 _proto_ 查找到 .then
          + 这里不懂 我看看抽时间写一篇 原型 原型链的博客吧
          + 不过这里 all 方法不需要定义在原型链上 本来就是 Promise 独有的 并且实例对象也用不到 all 方法
      */
      // let resAll = Promise.all([p1, p2])
      let resAll = Promise.prototype.all([p1, p2])
      console.log(resAll, 'resAll')
    </script>
    

Promise 构造函数

  • 创建一个 Promise 构造函数

    • 参数

      • 传入的是带两参数的回到函数 这里用 executor 接收

        • 本质其实是个执行函数 其中两个参数函数就是 下方定义的 resolve 和 reject
      • 并且这两个函数要通过 executor 调用一次

        // 这里没使用 resolve 是强调一下这个作为参数传到 Promise 内部 然后通过 executor 执行一下这个函数
        new Promise((so, re) => {
          so('ok)
        })
        
    • 属性

      • PromiseState 状态

      • PromiseResult 结果

      • callBacks 存储回调函数

        • 这里存储的是相应的回调函数
        • 当执行到 resolve 或者 reject 时 会将对应的 resolve 或者 reject 全部执行
        • 里面的细节会在下方结合代码说明
    • 构造函数会自动执行两个函数

      • resolve 成功的回调
      • reject 失败的回调
      • 1.因为状态不可逆 所以状态改变后直接返回
      • 2.修改相应的状态 和 值
      • 3.因为执行这个函数就意味着改变了状态 可以执行之前存储的回调函数
  • function Promise(executor) {
      // 添加属性
      this.PromiseState = 'pending'
      this.PromiseResult = nullthis.callbacks = [] // 存对象 对象中两个函数 onReject onResolve// 保存实例对象 this 的值
      let self = thisfunction resolve(data) {
        // 为了确保状态确定后不执行其他回调
        if (self.PromiseState !== 'pending') return
        // 修改对象的状态
        self.PromiseState = 'fulfilled'
        self.PromiseResult = data
        // 所有回调函数的结果都是由最后执行的这个 resolve 或者 reject 的值来决定
        // 模拟异步
        setTimeout(() => {
          self.callbacks.forEach(item => {
            item.onResolve(data)
          })
        }, 0)
      }
    ​
      function reject(data) {
        if (self.PromiseState !== 'pending') return
        // 修改对象的状态
        self.PromiseState = 'reject'
        self.PromiseResult = data
        setTimeout(() => {
          self.callbacks.forEach(item => {
            item.onReject(data)
          })
        }, 0)
      }
    ​
      // 同步调用 执行器函数
      try {
        // console.log(resolve, reject)
        executor(resolve, reject)
      } catch (e) {
        reject(e)
      }
    }
    

.then 方法

  • then 函数

    • 参数 两个回调函数

      • onResolve
      • onReject
    • 返回一个 Promise 对象

      • 因为返回值为 Promise 所以支持链式调用
      • p.then().then().then()
    • 根据不同的状态执行相应的函数

      • fulfilled

      • reject

        • 如果是结果了 那就直接返回 如果是 Promise 就继续 .then 调用
        // 这种情况返回的就是 Promise 需要继续判断
        p.then(res => {
        return new Promise()
        })
        
      • pending

        • 如果传递的是一个同步任务 是立刻改变状态的

        • 但是异步任务 那么需要改变状态以后才执行回调 所以这些回调函数 要在 resolve 或 reject 中执行

        • 这些回调函数状态还是 pending 不确定的 要先保存到 callBacks 中

        • 因为只要状态没改变就 push 所以可以支持多个回调

          • let p = new Promise()
            p.then
            p.then
            p.then
            
          • 链式回调是因为返回值为 Promise 的原因
  • Promise.prototype.then = function (onResolve, onReject) {
      let self = this
      /**
       * 这两个判断的作用是什么呢?举例吧
       * p.then().then(res => {})
       * 看出来了吧 要是前一个 then 不传参数就是 undefined 那我后面要执行 callBack 函数怎么执行?
       * 所以可以为空 但是给他添加一个默认函数
       */
      // 
      if (typeof onReject !== 'function') {
        onReject = reason => {
          throw reason
        }
      }
      if (typeof onResolve !== 'function') {
        onResolve = value => value
      }
      /**
       * 注意 链式调用的返回结果是一个新的 Promise
       * 而这个新的 promise 的值取决于上一个 promise 的返回值
       * 比如 上一个执行结果为 reject 那么
       */
      return new Promise((resolve, reject) => {
        // 根据状态调用对应得 回调
        function callBack(type) {
          // type 执行的是 .then 中的回调函数 返回 res
          // 而这个 res 是根据 上一个 Promise 的 PromiseState 和 PromiseResult 确定的
          // self 就说明了这一点
          // 这个新的 new Promise() 执行的回调会根据上一个 状态来执行 if else 中的回调函数
          // 唯一要看的是 pending 中添加的回调
          // 这里要注意的的是 上一个结果可能是 reject 但是对于这一个 Promise 来说 他已经决定状态了
          // 既然决定好状态了 那么就直接调用 resolve 就行了
          try { // 用 try catch 包裹 为了防止抛出错误直接终止运行了
            let res = type(self.PromiseResult)
            // console.log(res, 'in')
            // 获取当前调用的结果
            if (res instanceof Promise) {
              res.then(val => {
                resolve(val)
              }, rea => {
                reject(rea)
              })
            } else {
              resolve(res)
            }
          } catch(e) {
            reject(e + '-err')
          }
        }
        if (this.PromiseState === 'fulfilled') {
          // 模拟异步
          setTimeout(() => {
            callBack(onResolve)
          }, 0)
        }
        if (this.PromiseState === 'reject') {
          // 模拟异步
          setTimeout(() => {
            callBack(onReject)
          }, 0)
        }
        if (this.PromiseState === 'pending') {
          // 保存回调函数
          this.callbacks.push({
            onResolve: function () {
              callBack(onResolve)
            },
            onReject: function () {
              callBack(onReject)
            }
          })
        }
      })
    }
    

.catch 方法

  • catch 方法

    • 接收最后失败的回调

    • .then() 方法是可以接收最后失败的回调的

    • 这说明了什么呢 说明 .catch 方法是和 .then() 方法本质一样的

    • 那么区别是什么?

      • 就是他只接收失败的方法呗
  • Promise.prototype.catch = function (onReject) {
      return this.then(undefined, onReject)
    }
    

.all 方法

  • all 方法

    • 只有传入的函数全部成功才返回成功
  • 参数

    • 传入的是一个数组
  • 返回一个 Promise 对象

    • 要将 数组中的所有 promise 全部执行

    • 这里要使用一个 count 计数

    • 一个数组存储执行结果

      • 并且添加的结果要按下标添加 可能是异步函数 push 顺序会乱
  • Promise.prototype.all = function (promises) {
      return new Promise((resolve, reject) => {
        let count = 0
        let resArr = []
        for (let i = 0; i < promises.length; i++) {
          promises[i].then(res => {
            count++
            // 按顺序将对应结果放入结果数组
            resArr[i] = res
            // 只有全部成功的才执行成功回调
            if (count === promises.length) resolve(resArr)
          }, rea => {
            reject(rea)
          })
        }
      })
    }
    

race 方法

  • 这个就比较简单了
  • 直接遍历 谁先改变谁就直接执行
  • Promise.race = function (promises) {
      return new Promise((resolve, reject) => {
        for (let i = 0; i < promises.length; i++) {
          promises[i].then(res => {
            resolve(res)
          }, rea => {
            reject(rea)
          })
        }
      })
    }
    

总结

  • 整个流程还是挺复杂还 最好还是自己敲一遍 我之前没搞懂得地方有在相应代码位置注释 应该能够看懂吧
  • 还是得自己多花时间思考 自己理解了才是自己的 ❤❤❤