记录一下实现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个部分
- 捕获异常
@param {Promise} context @param {*} x - Promise实例的值 function resolvePromise(context, x) { try { ... } catch(reason) { rejectPromise(context, reason) } }- 处理值是当前Promise实例
- 处理Promise实例
- 处理thenable对象
值得注意的是对thenable对象的then方法的处理过程和传入Promise的函数的处理是一致的除了对then方法的处理需要绑定x作为其上下文
- 更新Promise状态,执行回调函数
-
rejectPromise
rejectPromise的过程和resolvePromise(5)的过程是一致的
Code
如果上面的代码片段不能让你很好理解的话,请参考具体实现