Promise实现

166 阅读3分钟

记录一下实现Promise的过程,希望能给其他小伙伴带来一些帮助,如有一些可以优化的地方望不吝赐教

Env

在这里我要说一下, Promise的实现是一个很简单的过程 如果你不是这样认为的话, 我猜测你可能没有看过A+规范

A+译文很明确的给出了实现Promise的过程, 我相信阅读A+规范要比阅读别人的代码更容易(除了你想从其他人的代码里找到一些编程思想)

实现过程

  • resolve和reject的过程是互斥的

    我们知道Promise的状态变更后就是确定的了,所以在实现resolvePromise/rejectPromise的过程中一定需要判断

    当前Promise的状态如果不是pendding时,直接return

    这就结束了么?

    笔者认为这样就足够让resolve和reject互斥,但是直到跑测试用例的时候才发现忽略了一种情况

    function delay() {
      return new Promise(resolve, setTimeout(() => resolve('success delay')), 1000);
    }
    
    new Promise((resolve, reject) => {
      resolve(delay());
      reject('fail');
    });
    
    // 这会在1s后返回一个resolve状态的promise
    

    这是因为在我的实现中对状态的改变是后置的,即处理完所有的情况(promise, thenable...)后才改变,这样是不会阻塞reject执行的, 所以需要对resolve和reject加锁

    在执行传入promise的回调函数中

    这样就保证了resolve和reject的逻辑互斥

  • then方法

    then方法返回一些新的Promise(promiseInstanceThen),并且将通过then方法注册的回调作为其值/拒因,

    可以想想到改变promiseInstanceThen的resolve,reject和回调一定是在microTask队列中某个函数的闭包成员

    所以要对注册的回调进行一次封装

    所以then方法就是这样的

    这个实现中的另一个重点在push方法下面会提到

  • 当Promise状态改变后的then方法

    看一下下面的例子

    const promiseInstance = new Promise(resolve => resolve('success'));
    
    promiseInstance.then((res) => console.log(res)); // output success
    

    当Promise实例的状态改变后也是可以调用then方法的,此时这个then方法在下一次eventLoop中执行, 所以需要一个特殊的队列处理这种情况

    push方法中会根据状态做差异化处理

    所以使用了这个队列的then方法会更简单/清晰

  • 特殊的值

    从规范中可以知道有一些特殊的值要进行一些特殊的处理

    • 当前Promise 实例
    • Promise 实例
    • thenable 对象且then是一个方法

    所以首先提供一下这3者的判断方法

    对于当前Promise 实例的判断只是一个比较引用的过程

    context === value
    
  • resolvePromise

    resolvePromise的过程可以分为5个部分

    1. 捕获异常
    @param {Promise} context
    @param {*} x - Promise实例的值
    function resolvePromise(context, x) {
      try {
        ...
      } catch(reason) {
        rejectPromise(context, reason)
      }
    }
    
    1. 处理值是当前Promise实例

    1. 处理Promise实例

    1. 处理thenable对象

    值得注意的是对thenable对象的then方法的处理过程和传入Promise的函数的处理是一致的

    除了对then方法的处理需要绑定x作为其上下文

    1. 更新Promise状态,执行回调函数

  • rejectPromise

    rejectPromise的过程和resolvePromise(5)的过程是一致的

Code

如果上面的代码片段不能让你很好理解的话,请参考具体实现