Promise-根据规范 手写实现

301 阅读8分钟

🔍重点:为什么需要一个数组来存放onFulfilled这个callback --> .then可以被多次执行 可以注册多个回调,状态改变的时候,多个回调会依次按照注册的顺序来执行

一、promiseA+规范梳理

1. Promise states

  • pending 初始状态

  • fulfilled / rejected 最终态,promise被resolve/reject后改变状态,且必须有value/resaon值

👉 【必须有value】 那么平时经常写的resolve() 实际相等于resolve了一个undefined。value可以是各种类型 undefined/boolean甚至是一个promise

1.1 状态流转

pending -> 通过resolve(value)方法->fullfilled

pending -> 通过reject(reason)方法 ->rejected

pending、fulfilled、rejected是状态(动作的结果) ; resolve和reject是动作

2. then

promise.then(onFulfilled, onRejected)

2.1 参数要求

传入的onFulfilled,onRejected 必须是函数,不是则忽略

2.2 onFulfilled 、onRejected特性

  • promise状态改变的时候 调用相应的回调 (状态变为fulfilled,回调这个onFulfilled,参数是value。rejected状态同理)

  • 状态改变之前回调不应该调用

  • 只能被调用一次 即状态改变后执行回调,回调后就不会再被调用了 🚩【实现思路:通过变量限制执行次数】

  • onFulfilled,onRejected 应该是微任务

2.3 then方法

可以被调用多次,promise状态变为fulfilled时,所有的onFulfilled都按照then的注册顺序执行;rejected状态同理 按照then中注册的onRejected顺序执行。举例 👇

🚩【按顺序执行cb的实现思路:各需要一个数组来存储onFulfilled以及onRejected的callback】

let promise = new Promise
promise.then(cb1).then(cb2).then(cb3)  //按注册顺序先执行cb1再执行cb2再执行cb3…

2.4 返回值

  • then的返回值是一个promise ,而且是一个新的promise (⚠️重点牢记
promise2 = promise1.then(onFulfilled,onRejected)
  • 回调函数onFulfilledonRejected的返回结果 (以下用cb代替数)
    • 若cb为函数
      1. 执行结果为x,调用resolvePromise
      2. cb onFulfilledonRejected执行抛出异常时,promise2需要被reject()
    • 若cb不为函数,上述说过应被忽略,那如何忽略呢?
      1. onFulfilled 不是函数 ,那么promise2以promise1的value来触发fulfilled状态

      2. onRejected 不是函数 promise 2以promise1的reason来触发rejected状态

      👉总结: 即cb不是函数 promise1有什么状态 promise2就跟风这个状态,指的是状态跟风 ,值得透传

image.png

2.5 resolvePromise

resolvePromise 真正解决promise返回结果的 解析promise的执行函数

resolvePromise(promise2,x,resolve,reject) 

// promise2:新生成的promise实例
// x :上述cb正常执行完的结果
// resolve,reject :上面传进来的可以更改状态的方法
  • promise2 和 x相等,造成死循环,reject出去并报错

  • 如果x是一个promise

    • 且x状态为pending,那么新的promise也必须是pending状态,直到x状态变成fulfilled或rejected

    • 且x状态为fulfilled,那么直接以x的状态给出去 fulfill the promise with the same value,类似上述的值得透传

    • 且x状态为rejected,reject the promise with the same reason

    总结:cb不为函数的情况 以及 cb的返回值x的promise状态 都有透传值和状态

  • 如果x是一个Object或者function ,则let then = x.then 判断这一步的赋值是否会报错

    • 如果x.then这一步出错了,假设是一个obj,那么obj.then最多产出undefined 为什么会出错呢?(可能修改了原型链
    • 如果then得到的是一个函数,那么就去改变这个then函数的指针,then.call(x,resolvePromiseFn,rejectPromiseFn)让当前调用then的this变成了x,所以此处相当于x.then(这样就指向x),并传入then里面所需的2个回调
      • resolvePromiseFn的入参是y, 执行resolvePromise(promise2, y, resolve, reject)
    • 如果调用then 抛出异常e,如果resolvePromiseFn,rejectPromiseFn已被执行完那么就不管了(因为状态改变后它的cb只会被执行一次),如果还没执行就以e为reason reject

    二、根据规范手写promise

思路整理:

  1. 定义promise的3个状态

  2. 设置初始属性状态

  3. 实现状态更改的两个函数 reject() resolve()

    fulfilled和rejected是最终态,所以但是两者之间是不能有状态流转的,所以resolve()和reject()方法中对于状态的判定 加一层条件 来判断之前过来的是否为pending状态

  4. 处理promise的入参 即const promise = new Promise( (resolve,reject)=>{} )传进来的callback

    4.1 这个fn接受resolve和reject两个参数

    4.2 fn执行时机:初始化promise的时候就要执行这个函数fn,只要报错就要立即reject 所以用try catch包裹。

    4.3 为什么通过bind去传值? 这个resolve()和reject在这个promise类中可以通过this去调用。但是传进来的fn如果不是箭头函数,只是一个普通函数,那么直接调用this.resolve的时候可能会发现外界的执行环境this上是没有resolve方法的

const promise = new Promise((resolve, reject) => {
  resolve()
})


// Promise里的cb  (resolve, reject) => { resolve() } 
// 类中接收并调用这个fn ,调用fn的时候传入参数,参数为class中改变状态的2个方法
// this.resolve.bind(this) --传给--> cb中的resolve形参
// this.reject.bind(this)  --传给--> cb中的reject形参
// 根据cb函数里执行resolve还是reject参数 来决定执行this.resolve还是this.reject方法


image.png

  1. then方法

    5.1 判断是否返回函数? 是则原样返回onFulfilled函数,不是则透传(接收什么返回什么,接收value返回value),写成函数也是为了接收什么返回什么

    5.2 then方法整体返回的是一个promise,比如promise2

    5.3 根据promise1不同状态 ,promise2的回调会执行不同的方法

    💬 点then的时候就会收集所有的cb,只不过还没调用

    比较容易想到的是switch分类 状态为fulfilled/ rejected执行对应回调。
    但是还有一种状态是pending
    
    解释:.then在promise实例后会立即注册,但是then里面的回调不会立即调用。
    
    
    例如:异步的时候才resolve 进去then 这个promise的状态还是pending
          所以先用数组把所有cb存起来
          
    

    5.4 若调用.then的时候promise1 为pending状态

    先存起来,当状态变为fulfilled和rejected的时候才去执行then里面的callback。
    a. 如果then的时候还是Pending就存入cb数组
    b. status变化的时候去执行存入数组中的回调--> set时候更新status,并判断新的status状态,fulfilled的则调用fulfilled存放的数组

    当状态发生变化的时候去执行什么...这个语义更适合使用getter()setter() 这种拦截触发器

image.png

  1. then里的cb执行抛出异常,promise2需要被reject() --规范中的第2.4条

    抛出异常try/catch image.png

  2. cb执行成功 返回值x,则运行resolvePromise(promise2,x,resolve,reject)

    7.1 判断x是否和promise2相等
    7.2 判断x是否为promise,是则让新的promise接收x的状态
    7.3 判断x是是否为对象or函数
    ①排除null ②try then=x.then ③then是个函数:一个回调只能执行一次,then.call ;不是,当个值直接resolve回去

image.png

  7.4 判断x是否为普通值

完整代码

// 1.定义三种状态
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class MPromise {
  FULFILLED_CALLBACK_LIST = [] //也是一种属性写法。编译结果仍然会把它绑定在this上
  REJECTED_CALLBACK_LIST = []
  _status = PENDING
  constructor(fn) {
    //2.设置初始状态
    this.status = PENDING
    this.value = null
    this.reason = null

    // 4.处理promise的入参 回调函数
    try {
      fn(this.reslove.bind(this), this.reject.bind(this))
    } catch (error) {
      this.reject(error)
    }
  }
  // 5.4 监听状态的变化来执行then里面的回调
  get status() {
    return this._status
  }
  set status(newStatus) {
    this._status = newStatus //更新状态
    //判断新的状态
    switch (newStatus) {
      case FULFILLED: {
        this.FULFILLED_CALLBACK_LIST.forEach((callback) => {
          callback(this.value)
        })
        break
      }
      case REJECTED: {
        this.REJECTED_CALLBACK_LIST.forEach((callback) => {
          callback(this.reason)
        })
        break
      }
    }
  }

  // 3.实现状态更改的两个函数 reslove和reject方法
  resolve(value) {
    if (this.status == PENDING) {
      this.value = value //先更新value
      this.status = FULFILLED //再更新状态,这样触发set status才能拿到最新value
      
    }
  }

  reject(reason) {
    if (this.status == PENDING) {
      this.reason = reason
      this.status = REJECTED
    }
  }

  // 5.then方法
  // 判断两个回调是否为函数,不是则忽略(透传)
  then(onFulfilled, onRejected) {
    const realOnFulfilled = this.isFunction(onFulfilled)
      ? onFulfilled
      : (value) => {
          return value
        }

    const realOnRejected = this.isFunction(onRejected)
      ? onRejected
      : (reason) => {
          throw reason
        }

    const promise2 = new MPromise((resolve, reject) => {
      //6. onFulfilled或onRejected执行抛出异常时,promise2需要被reject()
      const fulfilledMicrotask = () => {
        try {
          const x = realOnFulfilled(this.value)
          this.resolvePromise(promise2, x, resolve, reject)
        } catch (e) {
          reject(e)
        }
      }

      const rejectedMicrotask = () => {
        try {
          const x = realOnRejected(this.reason)
          this.resolvePromise(promise2, x, resolve, reject)
        } catch (e) {
          reject(e)
        }
      }
      switch (this.status) {
        case FULFILLED: {
          fulfilledMicrotask()
          break
        }
        case REJECTED: {
          rejectedMicrotask()
          break
        }
        case PENDING: {
          this.FULFILLED_CALLBACK_LIST.push(fulfilledMicrotask)
          this.REJECTED_CALLBACK_LIST.push(rejectedMicrotask)
          break
        }
      }
    })
    return promise2
  }

  isFunction(cb) {
    return typeof cb === 'function'
  }

  resolvePromise(promise2, x, resolve, reject) {
    if (promise2 == x) {
      return reject(
        new TypeError('The promise and the return value are the same')
      )
    }

    if (x instanceof MPromise) {
      //如果x是promise,那么让新的promise接收x的状态
      // 即继续执行x,如果执行又拿到了y,那么继续解析y
      //(return 出来是个promise就拿到外面来then执行相应的回调)
      x.then((y) => {
        this.resolvePromise(promise2, y, resolve, reject)
      }, reject)
    } else if (typeof x === 'object' || this.isFunction(x)) {
      //x是对象或函数
      if (x === null) {
        return resolve(x)
      }
      let then = null

      // 如果 x.then 这步出错,那么 reject promise with e as the reason.
      try {
        then = x.then
      } catch (error) {
        return reject(error)
      }

      // 如果获取到的then是个函数
      if (this.isFunction(then)) {
        let called = false //一个回调只能被调用一次,变量标记是否被调用

        try {
          then.call(
            x,
            (y) => {
              if (called) {
                return
              }
              called = true
              this.resolvePromise(promise2, y, resolve, reject)
            }, //onFulfilled
            (r) => {
              if (called) {
                return
              }
              called = true
              reject(r)
            } //onRejected
          )
        } catch (error) {
          if (called) {
            return
          }
          reject(error)
        }
      } else {
        resolve(x)
      }
    } else {
      //x为普通值
      resolve(x)
    }
  }
}


// 使用实现
const promise1 = new MPromise((resolve, reject) => {
  resolve()
})

const promise2 = promise1.then(
  (onFulfilled) => {},
  (onRejected) => {}
)