Promise原理解析

715 阅读7分钟

前言

  • 最近通过学习javascrip的异步处理了解到了Promise对象,并学习了对Promise原理的分析,以此记录!

Promise的原理解析

  • 通过封装库的思想手动构造一个Promise我学习到从以下几个方面去考虑
    1. Promise 中三种状态的实现
    2. Promise 中then的实现方法
    3. Promsie 中一些方法

一、Promise 的三种状态

  • Promise中默认 pending 状态,调取 resolve方法 成功返回 fulfilled 状态,调取 reject方法 成功返回 rejected 状态
// Promise类
 let p = new Promise((resolve, reject) => {
     // resolve('success')
     // reject('err')
  })
 console.log(p) // 返回Promise对象 其中[[PromiseState]]: "pending" [[PromiseResult]]: undefined

image.png
根据返回对象我们可以自己构造一个方法实现简单的展现Promise的状态

class MPromise {
        constructor(handle) {
          // 默认状态
          this['[[PromiseState]]'] = 'pending'
          this['[[PromiseResult]]'] = undefined
          handle(this.#resolve.bind(this), this.#reject.bind(this))
        }
        // 成功时调取函数
        #resolve(val) {
          this['[[PromiseState]]'] = 'fulfilled'
          this['[[PromiseResult]]'] = val
        }
        // 失败时调取函数
        #reject(err) {
          this['[[PromiseState]]'] = 'rejected'
          this['[[PromiseResult]]'] = err
        }
      }
      let p = new MPromise((resolve, reject) => {
        // resolve('success')
        reject('err')
      })
      console.log(p) // 

image.png
这样就实现了一个简易的呈现状态的Promise

二、then的问题

  • 多个 then 的实现
  • then 的返回值
  • 微任务宏任务
  • 链式操作

实现then方法

Promise中这样调取

let p = new Promise((resolve, reject) => {
        resolve('success')
        reject('err')
      })
      p.then((res) => {
        console.log(res) // success
      },err=>{
        console.log(err) // err
      })

手动构造一个 then 的方法 注意:then 方法不能立刻执行,当调取resolve或者reject之后才能执行 then 否则直接执行 then 延迟执行resolve或reject时会抛出undefined,代码如下:

class MPromise {
        constructor(handle) {
          // 默认状态
          this['[[PromiseState]]'] = 'pending'
          this['[[PromiseResult]]'] = undefined
          handle(this.#resolve.bind(this), this.#reject.bind(this))
        }
        // 成功时调取函数
        #resolve(val) {
          this['[[PromiseState]]'] = 'fulfilled'
          this['[[PromiseResult]]'] = val
        }
        // 失败时调取函数
        #reject(err) {
          this['[[PromiseState]]'] = 'rejected'
          this['[[PromiseResult]]'] = err
        }
        then(onResolved, onRejected) {
          if ((this['[[PromiseState]]'] = 'fulfilled')) {
            onResolved && onResolved(this['[[PromiseResult]]'])
          } else {
            onRejected && onRejected(this['[[PromiseResult]]'])
          }
        }
      }
      let p = new MPromise((resolve, reject) => {
        setTimeout(() => {
          resolve('success')
          // reject('err')
        })
      })
      p.then((res) => {
        console.log(res)
      }

image.png 那么解决这一问题的方法就是在调取resolve或者reject后执行,代码如下:

class MPromise {
        constructor(handle) {
          // 默认状态
          this['[[PromiseState]]'] = 'pending'
          this['[[PromiseResult]]'] = undefined
          this.resolveFn = undefined
          this.rejectFn = undefined
          handle(this.#resolve.bind(this), this.#reject.bind(this))
        }
        // 成功时调取函数
        #resolve(val) {
          this['[[PromiseState]]'] = 'fulfilled'
          this['[[PromiseResult]]'] = val
          this.resolveFn(val)
        }
        // 失败时调取函数
        #reject(err) {
          this['[[PromiseState]]'] = 'rejected'
          this['[[PromiseResult]]'] = err
          this.resolveFn(val)
        }
        then(onResolved, onRejected) {
          // 将状态保存起来
          this.resolveFn = onResolved
          this.rejectFn = onRejected
        }
      }

这样我们延迟调用resolve或reject就不会出现undefined现象

多个then的问题

上面的方法虽然解决延时调用的问题,但如果我们调用多个 then 方法会出现覆盖问题,总是获取到最后一次调取的值,所以多个 then 的解决方法就是通过数组存储,依次执行

   class MPromise {
        constructor(handle) {
          // 默认状态
          this['[[PromiseState]]'] = 'pending'
          this['[[PromiseResult]]'] = undefined
          this.resolveQueue = []
          this.rejectQueue = []
          handle(this.#resolve.bind(this), this.#reject.bind(this))
        }
        // 成功时调取函数
        #resolve(val) {
          this['[[PromiseState]]'] = 'fulfilled'
          this['[[PromiseResult]]'] = val
          const run = () => {
            let cb
            while ((cb = this.resolveQueue.shift())) {
              cb && cb(val)
            }
          }
          run()
        }
        // 失败时调取函数
        #reject(err) {
          this['[[PromiseState]]'] = 'rejected'
          this['[[PromiseResult]]'] = err
          const run = () => {
            let cb
            while ((cb = this.resolveQueue.shift())) {
              cb && cb(err)
            }
          }
          run()
        }
        then(onResolved, onRejected) {
          // 将状态保存起来
          this.resolveQueue.push(onResolved)
          this.rejectQueue.push(onRejected)
        }
      }
      let p = new MPromise((resolve, reject) => {
        setTimeout(() => {
          resolve('success')
          // reject('err')
        })
      })
      p.then((res) => {
        console.log(res)
      })
      p.then((res) => {
        console.log('22')
      })

image.png
但是这样还有一个执行顺序问题,如果同步执行的话,最后执行 then 方法,导致同步执行没有结果,解决这一问题的方法就是先执行 then 方法,所以让resolve和reject变成异步方法,代码如下:

// 将上面代码中run方法修改为异步方法即可
setTimeout(run)

这样不论是同步还是异步,调用 then 都会获取结果

微任务与宏任务

上面我们解决了同步和异步时都会调用 then 方法的问题,然而还有一个问题需要解决,先看问题:

// 输出顺序问题
// Promise
setTimeout(() => {
        console.log('11')
      })
      console.log('22')
      let p = new Promise((resolve, reject) => {
        resolve('33')
      })
      p.then((res) => {
        console.log(res)
      })
      p.then((res) => {
        console.log('44')
      })
      console.log('55')
// 输出顺序为 22-55-33-44-11
// 自定义方法
// MPromise为上述方法
 setTimeout(() => {
        console.log('11')
      })
      console.log('22')
      let p = new MPromise((resolve, reject) => {
        resolve('33')
      })
      p.then((res) => {
        console.log(res)
      })
      p.then((res) => {
        console.log('44')
      })
      console.log('55')
// 输出顺序为 22-55-11-33-44
  • 微任务:一个需要异步执行的函数,执行时机是在主函数执行结束自后、当前宏任务结束之前
  • 宏任务:宏任务的时间粒度比较大,执行的时间间隔是不能精确控制的,对一些高实时性的需求就不太符合 then 方法是一个微任务
  • 执行时 首先是同步代码执行,所以先输出 22 55 之后执行异步代码,因为一个宏任务执行后要先执行它所包含的微任务,然后在去执行下一个宏任务,setTimeout属于下一个宏任务,所以先输出 33 44 那如何修改自己构造的方法呢?
    MutationObserver 方法
    developer.mozilla.org/zh-CN/docs/…

利用这个方法我们可以通过监听属性变化,进行函数调用,变成异步微任务,代码如下:

class MPromise {
        constructor(handle) {
          // 默认状态
          this['[[PromiseState]]'] = 'pending'
          this['[[PromiseResult]]'] = undefined
          this.resolveQueue = []
          this.rejectQueue = []
          handle(this.#resolve.bind(this), this.#reject.bind(this))
        }
        // 成功时调取函数
        #resolve(val) {
          this['[[PromiseState]]'] = 'fulfilled'
          this['[[PromiseResult]]'] = val
          const run = () => {
            let cb
            while ((cb = this.resolveQueue.shift())) {
              cb && cb(val)
            }
          }
          const observe = new MutationObserver(run)
          observe.observe(document.body, { attributes: true })
          document.body.setAttribute('name', 'a')
        }
        // 失败时调取函数
        #reject(err) {
          this['[[PromiseState]]'] = 'rejected'
          this['[[PromiseResult]]'] = err
          const run = () => {
            let cb
            while ((cb = this.resolveQueue.shift())) {
              cb && cb(err)
            }
          }
          const observe = new MutationObserver(run)
          observe.observe(document.body, { attributes: true })
          document.body.setAttribute('name', 'a')
        }
        then(onResolved, onRejected) {
          // 将状态保存起来
          this.resolveQueue.push(onResolved)
          this.rejectQueue.push(onRejected)
        }
      }
      setTimeout(() => {
        console.log('11')
      })
      console.log('22')
      let p = new MPromise((resolve, reject) => {
        resolve('33')
      })
      p.then((res) => {
        console.log(res)
      })
      p.then((res) => {
        console.log('44')
      })
      console.log('55')
      // 通过MutationObserver模拟一个微任务,所以输出顺序就是 22-55-33-44-11

链式操作

先来看一下 Promise 中 then 的链式操作

 let p = new Promise((resolve, reject) => {
        resolve('success')
      })
      p.then((res) => {
        console.log(res)
        return new Promise((resolve, reject) => {
          resolve('success')
        })
      }).then((res) => {
        console.log(res)
      })
   // 打印两个success

也就是说在 Promise 的 then 中可以通过 return 返回并通过下一个 then 调取,所以我的思路就是先让自己构造的 then 方法可以返回一个新包装的 Promise 对象 代码如下:

class MPromise {
        constructor(handle) {
          // 默认状态
          this['[[PromiseState]]'] = 'pending'
          this['[[PromiseResult]]'] = undefined
          this.resolveQueue = []
          this.rejectQueue = []
          handle(this.#resolve.bind(this), this.#reject.bind(this))
        }
        // 成功时调取函数
        #resolve(val) {
          this['[[PromiseState]]'] = 'fulfilled'
          this['[[PromiseResult]]'] = val
          const run = () => {
            let cb
            while ((cb = this.resolveQueue.shift())) {
              cb && cb(val)
            }
          }
          const observe = new MutationObserver(run)
          observe.observe(document.body, { attributes: true })
          document.body.setAttribute('name', 'a')
        }
        // 失败时调取函数
        #reject(err) {
          this['[[PromiseState]]'] = 'rejected'
          this['[[PromiseResult]]'] = err
          const run = () => {
            let cb
            while ((cb = this.resolveQueue.shift())) {
              cb && cb(err)
            }
          }
          const observe = new MutationObserver(run)
          observe.observe(document.body, { attributes: true })
          document.body.setAttribute('name', 'a')
        }
        then(onResolved, onRejected) {
          return new MPromise((resolve, reject) => {
            // 拿到结果 且不可立即执行
            let resolveFn = function (val) {
              let result = onResolved && onResolved(val)
              // 返还MPromise对象
              if (result instanceof MPromise) {
                result.then(resolve)
              } else {
                resolve(result)
              }
            }
            this.resolveQueue.push(resolveFn)

            let rejectFn = function (err) {
              onRejected && onRejected(err)
              reject(err)
            }
            this.rejectQueue.push(rejectFn)
          })
          // 将状态保存起来
        }
      }

      let p = new MPromise((resolve, reject) => {
        resolve('33')
      })
      p.then((res) => {
        console.log(res)
        return new MPromise((resolve) => {
          resolve('11')
        })
      }).then((res) => {
        console.log(res)
      })
      // 结果打印 33,11

三、周边方法

原型方法

catch
catch主要是通过 then 方法实现的 通过给 then 中的onReject传入值即可

 catch(fn) {
          return this.then(undefined, fn)
        }
 let p = new MPromise((resolve, reject) => {
     reject('err')
 })
 p.then((res) => {
     console.log(res)
 }).catch((err) => {
     console.log(err) // err
 })

finally

finally(cb) {
    this.then(cb, cb)
}

finally方法放在then后面执行就好

静态方法

resolve/reject
也是通过返回一个 MPromise 对象来构造的方法

static resolve(val) {
          return new MPromise((resolve) => {
            resolve(val)
          })
        }
static reject(err) {
         return new MPromise((resolve, reject) => {
            reject(err)
         })
        }
let p = MPromise.reject('err')
console.log(p) // 返回一个 MPromise 对象
        

race
race 的参数是一个包含Promise对象的数组

let p1 = new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve('success')
        }, 2000)
      })
      let p2 = new Promise((resolve, reject) => {
        setTimeout(() => {
          reject('err')
        }, 1000)
      })
      Promise.race([p1, p2]).then(
        (res) => {
          console.log(res)
        },
        (err) => {
          console.log(err)
        }
      )
      // 结果打印 err

自己实现的思路就是定义一个静态函数返回Promise对象

static race(lists) {
          return new MPromise((resolve, reject) => {
            lists.forEach((item) => {
              item.then(
                (res) => {
                  resolve(res)
                },
                (err) => {
                  reject(err)
                }
              )
            })
          })
        }

all
只返回resolve中的值

 static all(lists) {
          let resArr = []
          let num = 0
          return new MPromise((resolve) => {
            lists.forEach((item) => {
              item.then((res) => {
                num++
                resArr.push(res)
                if (resArr.length === lists.length) {
                  resolve(resArr)
                }
              })
            })
          })
        }

allSettled
allSettled 会把 Promise 成功或者失败的值都获取到

 let p1 = new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve('success')
        }, 1000)
      })
      let p2 = new Promise((resolve, reject) => {
        setTimeout(() => {
          reject('err')
        }, 2000)
      })
      Promise.allSettled([p1, p2]).then((res) => {
        console.log(res)
      })

image.png 手动实现 allSettled 方法

  static allSettled(lists) {
          let resArr = new Array(lists.length)
          let num = 0
          return new MPromise((resolve,reject) => {
            lists.forEach((item, key) => {
              let obj = {}
              item.then(
                (res) => {
                  obj['statue'] = 'fulfilled'
                  obj['val'] = res
                  resArr[key] = obj
                  num++
                  if (num >= lists.length) {
                    resolve(resArr)
                  }
                },
                (err) => {
                  obj['statue'] = 'rejected'
                  obj['val'] = err
                  resArr[key] = obj
                  num++
                  if (num >= lists.length) {
                    reject(resArr)
                  }
                }
              )
            })
          })
        }

总结

个人觉得对 Promise 的理解可能还是没有很深入的理解,毕竟刚刚入门 希望看到这篇文章的人有什么想法能够和笔者交流,有问题的地方及时指出,共同进步!