手撸简版Promise

手撸简版Promise

明白了异步逻辑,学会了原生promise使用之后,我们来手写实现一个简版Promise

五、动手实现一个简版Promise

5.1 开始之前

首先来分析一下我们需要做什么:

  • 首先要新建一个Promise类,它接收一个执行器函数,并立即执行执行它
  • Promise的实例有三种状态:
    • pending
    • fulfilled
    • rejected
  • Promise的实例有两种状态转变的可能
    • pending👉fulfilled
    • pending👉rejected
  • Promise的实例有两个方法来改变其状态,分别是resolvereject
  • Promise的实例有一个then方法,它可以链式调用,并且根据Promise实例的状态判断调用两个回调函数
  • Promise的实例有一个catch方法,是then方法在拒绝状态的一种特殊的形式
  • Promise有静态方法Promise.allPromise.racePromise.resolvePromise.reject

5.2 实现Promise基本结构

原生promise可以通过new来构造,因此这里我们选择类来实现

第一步:构建类和执行器

executor作为promise的执行器,它以resolvereject作为参数,也就是我们原生Promise的参数

// 构建Promise类和excutor执行器
class MyPromise {
  constructor(executor) {
      
    // resolve
    const resolve = () => {
      console.log('resolve')
    }
    
    // reject
    const reject = () => {
      console.log('reject')
    }
    
    // 立即执行执行器函数,出错则调用reject捕获错误
    try {
      executor(resolve, reject)
    } catch(error) {
      reject(error)
    }
  }
}

// 检验是否能运行
const p = new MyPromise((resolve, reject) => {
  console.log('test')
  resolve()
  reject()
})

// 打印结果如下,非常成功
// test
// resolve
// reject

第二步:加入状态管理

注意resolvereject的作用可以理解为仅仅改变状态和暴露值/原因,他不会终止后面的代码运行,且状态只能进行一次不可逆的转变。

class MyPromise {
  constructor(executor) {
    // +++++ 初始化状态 +++++
    this._status = STATUS.PENDING // promise初始状态为pending
    this._value = void 0 // 调用then回调的第一个参数的返回值
    this._reason = void 0 // 调用then回调的第二个参数的返回值
      
    // +++++ resolve => 在状态为pending时,将状态改成 fulfilled +++++
    const resolve = value => {
      if (this._status === STATUS.PENDING) {
        this._status = STATUS.FULFILLED // 更改状态
        this._value = value // 储存当前值,用于then回调
      }
    }
    
    // +++++ reject 在状态为pending时,将状态改成 rejected +++++
    const reject = reason => {
      if (this._status === STATUS.PENDING) {
        this._status = STATUS.REJECTED
        this._reason = reason
      }
    }
    
    // 立即执行执行器函数,出错则调用reject捕获错误
    // ......
  }
}

// 测试运行
const p = new MyPromise((resolve, reject) => {
  console.log('test')
  resolve()
  reject()
})
console.log(p)

// 状态也改变了,还只改变了一次,没问题
// test
// MyPromise {
//   _status: 'fulfilled',
//   _value: undefined,
//   _reason: undefined
// }

第三步:简单then方法初步实现

then方法接受两个函数作为参数,并根据定型的结果判断调用哪个回调

  // +++++ 类方法then +++++
  then(onFulfilled, onRejected) {
    // 对传入的参数进行检查,如果不是回调函数:在成功回调中直接返回,在失败回调中作为错误抛出
    onFulfilled =
      typeof onFulfilled === 'function' ? onFulfilled : value => value
    onRejected =
      typeof onRejected === 'function'
        ? onRejected
        : reason => {
            throw reason
          }

    // 根据状态判断调用哪个回调
    if (this._status === STATUS.FULFILLED) {
      // 成功时调用onFulfilled回调
      onFulfilled(this._value)
    } else if (this._status === STATUS.REJECTED) {
      // 失败时调用onRejected回调
      onRejected(this._reason)
    }
  }

以上实现了Promise的基本结构,生成promise实例时可以自动执行入参函数,实现了resolvereject对状态的更改和then方法的回调调用,但并没有实现异步逻辑

5.2 加入异步逻辑

ES6有宏任务和微任务两种异步模式,promise的then方法属于微任务,会在下一个宏任务前插队运行,我们这里统一使用setTimeout来模拟实现promise的异步逻辑

第一步:通过setTimeout实现then的异步
  // 类方法then
  then(onFulfilled, onRejected) {
    // ......
    // 根据状态判断调用哪个回调
    if (this._status === STATUS.FULFILLED) {
      // 成功时调用onFulfilled回调
      // +++++ 通过setTimeout实现异步 +++++
      setTimeout(() => onFulfilled(this._value), 0)
    } else if (this._status === STATUS.REJECTED) {
      // 失败时调用onRejected回调
      // +++++ 通过setTimeout实现异步 +++++
      setTimeout(() => onRejected(this._reason), 0)
    }
  }
第二步:解决定时器中定型情况,加入异步队列

onFulfilledonRejected加入异步逻辑后,如果执行器函数executor也通过异步的代码对promise进行定型呢?那么可能会出现状态还是pending就走到了then里,那么then里你写的在fulfilled状态和rejected状态下才会执行的代码可能永远跑不了

// 像这样,是不会resolve的
const p = new MyPromise((resolve, reject) => {
  setTimeout(() => resolve(1), 1)
})
p.then(console.log, null) // 啥也不会打印

这里我们可以等到它在定型之后再去运行,但是考虑到我们可以对同一个promise使用不同的回调调用多次then方法,因此可以考虑使用数组来存储回调,我们需要加入两个队列,分别存储成功的回调和失败的回调

  constructor(executor) {
    // ...
    // +++++ 记录回调的队列 +++++
    this._resolveQueue = [] // resolve时触发的成功队列
    this._rejectQueue = [] // reject时触发的失败队列
    // ...
  }

then的回调中增加pending状态的处理

  then(onFulfilled, onRejected) {
    // ...
    // +++++ 如果是pending状态则加入队列 +++++
    if (this._status === STATUS.PENDING) {
      this._onFulfilledQueue.push(onFulfilled)
      this._onRejectedQueue.push(onRejected)
    } 
    // ...
  }
第三步:在resolvereject改变状态后,处理异步回调队列

这里加入了回调队列的调用

    // resolve => 在状态为pending时,将状态改成 fulfilled
    const resolve = value => {
      if (this._status === STATUS.PENDING) {
        this._status = STATUS.FULFILLED // 更改状态
        this._value = value // 储存当前值,用于then回调
      }

      // +++++ 改变状态后,如果有then的回调还没处理,那么处理它 +++++
      while (this._onFulfilledQueue.length) {
        const callback = this._onFulfilledQueue.shift()
        callback(value)
      }
    }

    // reject 在状态为pending时,将状态改成 rejected
    const reject = reason => {
      if (this._status === STATUS.PENDING) {
        this._status = STATUS.REJECTED
        this._reason = reason
      }

      // +++++ 改变状态后,如果有then的回调还没处理,那么处理它 +++++
      while (this._onRejectedQueue.length) {
        const callback = this._onRejectedQueue.shift()
        callback(reason)
      }
    }

再测试一下之前失败的例子,打印成功了,说明是有效果的

// 像这样,是不会resolve的
const p = new MyPromise((resolve, reject) => {
  setTimeout(() => resolve(1), 1)
})
p.then(console.log, null) // 1
第四步:给promise定型方法resolvereject加上异步

resolvereject是在时间循环的末尾执行的,这里我们得加上异步setTimeout实现

    // resolve => 在状态为pending时,将状态改成 fulfilled
    const resolve = value => {
      if (this._status === STATUS.PENDING) {
        // +++++ 在事件循环末尾执行 +++++
        setTimeout(() => {
          this._status = STATUS.FULFILLED // 更改状态
          this._value = value // 储存当前值,用于then回调

          // 改变状态后,如果有then的回调还没处理,那么处理它
          while (this._onFulfilledQueue.length) {
            const callback = this._onFulfilledQueue.shift()
            callback(value)
          }
        }, 0)
      }
    }

    // reject 在状态为pending时,将状态改成 rejected
    const reject = reason => {
       // +++++ 在事件循环末尾执行 +++++
      setTimeout(() => {
        if (this._status === STATUS.PENDING) {
          this._status = STATUS.REJECTED
          this._reason = reason
        }

        // 改变状态后,如果有then的回调还没处理,那么处理它
        while (this._onRejectedQueue.length) {
          const callback = this._onRejectedQueue.shift()
          callback(reason)
        }
      }, 0)
    }

目前,完整代码是这样的:

// 定义promise的三种状态
const STATUS = {
  PENDING: 'pending',
  FULFILLED: 'fulfilled',
  REJECTED: 'rejected',
}

// 新建MyPromise类,它接受一个执行器函数executor
class MyPromise {
  constructor(executor) {
    this._status = STATUS.PENDING // promise初始状态为pending
    this._value = void 0 // 调用then回调的第一个参数的返回值
    this._reason = void 0 // 调用then回调的第二个参数的返回值
    this._onFulfilledQueue = [] // resolve时触发的成功队列
    this._onRejectedQueue = [] // reject时触发的失败队列

    // resolve => 在状态为pending时,将状态改成 fulfilled
    const resolve = value => {
      if (this._status === STATUS.PENDING) {
        setTimeout(() => {
          this._status = STATUS.FULFILLED // 更改状态
          this._value = value // 储存当前值,用于then回调

          // 改变状态后,如果有then的回调还没处理,那么处理它
          while (this._onFulfilledQueue.length) {
            const callback = this._onFulfilledQueue.shift()
            callback(value)
          }
        }, 0)
      }
    }

    // reject 在状态为pending时,将状态改成 rejected
    const reject = reason => {
      setTimeout(() => {
        if (this._status === STATUS.PENDING) {
          this._status = STATUS.REJECTED
          this._reason = reason
        }

        // 改变状态后,如果有then的回调还没处理,那么处理它
        while (this._onRejectedQueue.length) {
          const callback = this._onRejectedQueue.shift()
          callback(reason)
        }
      }, 0)
    }

    // 立即执行执行器函数,出错则调用reject捕获错误
    try {
      executor(resolve, reject)
    } catch (error) {
      reject(error)
    }
  }

  // 类方法then
  then(onFulfilled, onRejected) {
    // 对传入的参数进行检查,如果不是回调函数:在成功回调中直接返回,在失败回调中作为错误抛出
    onFulfilled =
      typeof onFulfilled === 'function' ? onFulfilled : value => value
    onRejected =
      typeof onRejected === 'function'
        ? onRejected
        : reason => {
            throw reason
          }

    // 如果是pending状态则加入队列
    if (this._status === STATUS.PENDING) {
      this._onFulfilledQueue.push(onFulfilled)
      this._onRejectedQueue.push(onRejected)
    }

    // 根据状态判断调用哪个回调
    if (this._status === STATUS.FULFILLED) {
      // 成功时调用onFulfilled回调
      setTimeout(() => onFulfilled(this._value), 0)
    } else if (this._status === STATUS.REJECTED) {
      // 失败时调用onRejected回调
      setTimeout(() => onRejected(this._reason), 0)
    }
  }
}

const p = new MyPromise((resolve, reject) => {
  setTimeout(() => resolve('马上就要成功了'), 1000)
})

p.then(console.log) // 马上就要成功了

至此,我们现在已经给Promise加如了异步逻辑,但明显还是不够的,接下来我们要让他能够链式调用起来。

5.3 实现then的链式调用

如果要实现链式调用,那么我们就得让它返回一个和这个实例相同类型的实例,就都有了then方法,那不就可以循环调用了?因此我们的then方法也需要返回一个Promise实例。看一下这个ES6中promise链式调用的例子:

const p = new Promise((resolve, reject) => {
    resolve(1)
})
p.then(res => {
    console.log('then1:', res) // 1
    return 2
}).then(res => {
    console.log('then2:', res) // 2
})

其实,这个得分情况讨论:

  • 如果上一个promise是成功fulfilled状态,那么可能会调用onFulfilled回调,那么:
    • 如果onFulfilled是函数,那么要使用onFulfilled对上一个promise暴露值进行处理,根据其返回值决定下一步,如果返回值是promise那么得调用then方法将它展开,否则直接暴露出去
    • 如果onFulfilled不是函数,要直接暴露value,即resolve(value)
  • 如果上一个promise是拒绝rejected状态,那么可能会调用onRejected回调,那么:
    • 如果onRejected是函数,那么要使用onRejected进行错误处理,根据其返回值决定下一步,如果返回值是promise那么得调用then方法将它展开,否则直接暴露出去
    • 如果onRejected不是函数,要直接暴露reason,即resolve(reason)
  • 如果上一个promise是pending状态,那么和前面一样分情况讨论加入队列即可

还有什么需要注意的呢?执行的成功或者失败的回调可能会产生又一个promise或者thenable类型的对象,那么必须要对其进行展开才行,我们用方法resolvePromise进行判断。最终实现结果如下,这里之前使用的if...else可以改成switch...case相对美观,顺手修改,详细操作见注释

// 定义promise的三种状态
const STATUS = {
  PENDING: 'pending',
  FULFILLED: 'fulfilled',
  REJECTED: 'rejected',
}

class MyPromise {
  constructor(executor) {
    this._status = STATUS.PENDING // promise初始状态为pending
    this._value = void 0 // 调用then回调的第一个参数的返回值
    this._reason = void 0 // 调用then回调的第二个参数的返回值
    this._onFulfilledQueue = [] // resolve时触发的成功队列
    this._onRejectedQueue = [] // reject时触发的失败队列

    // resolve => 在状态为pending时,将状态改成 fulfilled
    const resolve = value => {
      if (this._status === STATUS.PENDING) {
        setTimeout(() => {
          this._status = STATUS.FULFILLED // 更改状态
          this._value = value // 储存当前值,用于then回调

          // 改变状态后,如果有then的回调还没处理,那么处理它
          while (this._onFulfilledQueue.length) {
            const callback = this._onFulfilledQueue.shift()
            callback(value)
          }
        }, 0)
      }
    }

    // reject 在状态为pending时,将状态改成 rejected
    const reject = reason => {
      setTimeout(() => {
        if (this._status === STATUS.PENDING) {
          this._status = STATUS.REJECTED
          this._reason = reason
        }

        // 改变状态后,如果有then的回调还没处理,那么处理它
        while (this._onRejectedQueue.length) {
          const callback = this._onRejectedQueue.shift()
          callback(reason)
        }
      }, 0)
    }

    // 立即执行执行器函数,出错则调用reject捕获错误
    try {
      executor(resolve, reject)
    } catch (error) {
      reject(error)
    }
  }

  // 类方法then
  then(onFulfilled, onRejected) {
    // 对传入的参数进行检查,如果不是回调函数:在成功回调中直接返回,在失败回调中作为错误抛出
    onFulfilled =
      typeof onFulfilled === 'function' ? onFulfilled : value => value
    onRejected =
      typeof onRejected === 'function'
        ? onRejected
        : reason => {
            throw reason
          }

    // 被返回的新的promise
    const thenPromise = new MyPromise((resolve, reject) => {
      // 根据状态判断调用哪个回调
      switch (this._status) {
        // 成功时
        case STATUS.FULFILLED:
          setTimeout(() => {
            try {
              // +++++调用onFulfilled回调,看返回值做动作+++++
              let cbr = onFulfilled(this._value)
              resolvePromise(thenPromise, cbr, resolve, reject)
            } catch (err) {
              // 出错了,暴露错误
              reject(err)
            }
          }, 0)
          break

        // 失败时调用onRejected回调
        case STATUS.REJECTED:
          setTimeout(() => {
            try {
              // +++++调用onRejected回调,看返回值做动作+++++
              let cbr = onRejected(this._reason)
              resolvePromise(thenPromise, cbr, resolve, reject)
            } catch (err) {
              // 出错了,暴露错误
              reject(err)
            }
          }, 0)
          break

        //  +++++如果是pending状态则加入队列,也不能落下返回值的讨论+++++
        case STATUS.PENDING:
          // +++++成功队列+++++
          this._onFulfilledQueue.push(() => {
            try {
              let cbr = onFulfilled(this._value)
              resolvePromise(thenPromise, cbr, resolve, reject)
            } catch (err) {
              reject(err)
            }
          })

          // +++++失败队列+++++
          this._onRejectedQueue.push(() => {
            try {
              let cbr = onRejected(this._reason)
              resolvePromise(thenPromise, cbr, resolve, reject)
            } catch (err) {
              reject(err)
            }
          })
          break
      }
    })

    // 返回一个promise
    return thenPromise
  }
}

// +++++根据回调返回值处理暴露结果+++++
function resolvePromise(thenPromise, cbr, resolve, reject) {
  // 如果回调函数返回了本身就出现了循环引用,自己拒绝并暴露错误TypeError
  if (cbr === thenPromise) {
    return reject(new TypeError('循环引用了自己!'))
  }

  // 如果回调函数返回了promise值,则需要将其展开
  if (cbr instanceof MyPromise) {
    if (cbr._status === STATUS.PENDING) {
      // 如果cbr还是pending状态,我们需要等到它真的定型之后拿到它去做判断
      // 相当于这里只是做了等待处理
      cbr.then(realCBR => {
        return resolvePromise(thenPromise, realCBR, resolve, reject)
      }, reject)
    } else if (cbr._status === STATUS.FULFILLED) {
      // 如果cbr处于成功状态,则拿到cbr的成功值并暴露出去
      return resolve(cbr._value)
    } else if (cbr._status === STATUS.REJECTED) {
      // 如果cbr处于拒绝状态,则拿到cbr的拒绝原因并暴露出去
      return reject(cbr._reason)
    }
  }

  // 如果回调函数返回了一个对象或者函数
  else if (
    cbr !== null &&
    (typeof cbr === 'object' || typeof cbr === 'function')
  ) {
    // 这里主要是判断是不是thenable并根据情况进行处理,判断方法就是前一篇文章提到的鸭子类型检测
    // 通过try看他是不是有then,如果不是就拒绝并暴露错误
    let then
    try {
      then = cbr.then
    } catch (e) {
      return reject(e)
    }

    // 如果cbr是thenable类型的对象
    if (typeof then === 'function') {
      // 这里CBR还是只能转型一次,并且如果重复调用还是只能用第一次的结果
      let called = false // 标记调用情况
      try {
        // 把它当promise用,并作用在cbr上即可
        then.call(
          cbr,
          realCBR => {
            if (called) return
            called = true
            return resolvePromise(thenPromise, realCBR, resolve, reject)
          },
          realREJ => {
            if (called) return
            called = true
            return reject(realREJ)
          }
        )
      } catch (e) {
        // 出现错误判断是不是因为重复调用,如果是直接忽略跳过,如果不是暴露错误
        if (called) return
        called = true
        return reject(e)
      }
    }

    // 如果then不是函数,直接定型为成功状态,并暴露cbr
    else {
      return resolve(cbr)
    }
  }

  // 如果是其他普通值(不是对象和函数)
  else return resolve(cbr)
}

最终完成了promise的then方法

5.4 其他静态方法实现

5.4.1 静态方法resolve
  // MyPromise.resolve
  static resolve(value) {
    if (value instanceof MyPromise) {
      return value
    }

    return new MyPromise(resolve => {
      resolve(value)
    })
  }
5.4.2 静态方法reject
  // MyPromise.reject
  static reject(reason) {
    return new MyPromise((resolve, reject) => {
      reject(reason)
    })
  }
5.4.3 静态方法all
  // MyPromise.all
  static all(promiseArr) {
    let count = 0
    const result = []
    return new MyPromise((resolve, reject) => {
      if (!promiseArr.length) {
        return resolve(result)
      }
      promiseArr.forEach((p, i) => {
        MyPromise.resolve(p).then(
          value => {
            count++
            result[i] = value
            if (count === promiseArr.length) {
              resolve(result)
            }
          },
          err => {
            reject(err)
          }
        )
      })
    })
  }
5.5.4 静态方法race
  // MyPromise.race
  static race(promiseArr) {
    return new MyPromise((resolve, reject) => {
      promiseArr.forEach(p => {
        MyPromise.resolve(p).then(
          value => {
            resolve(value)
          },
          err => {
            reject(err)
          }
        )
      })
    })
  }
  • 8
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值