手写 Promise 及相关代码理解
前言
Promise 在日常开发中经常使用,但内部原理不太 理解,网上常看见一些源码的解析,得空了,便想看看相关的实现,对于之后的开发必然是有帮助的。
该理解为阅读他人博客之后的精简部分,用于本人理解,不一定适用于初学者。
1. 分析
1.1 极精简版
首先,我们看下 Promise 是如何使用的
new Promise((resolve, reject) => {
resolve('value')
})
.then(res => {
// 处理 resolve
}, err => {
// 处理 reject
})我们知道 Promise 有三个状态,默认为 pending, 还有 resolved 和 rejected.
由以上使用方式可知,Promise 是一个可以被 new 进行实例化的类,该类初始化接受一个函数,函数具有两个参数,一个函数将 Promise 状态变为 resolved, 一个将状态转变为 rejected。
实例之后具有 .then 方法,该方法同样可以接受两个参数,分别处理resolved和 rejected 之后的 Promise。再依据 Promises/A+ 规范, ,我们可以写出以下代码:
以上代码,即简单的实现了 Promise 的功能。
但是,经常我们在 Promise 的构造函数中,并非是立即进行 resolve 或者 reject, 而是存在异步 resolve 的情况,于是我们继续改进.
1.2 支持异步
其实支持异步的逻辑也很简单,即当 .then 执行的时候,Promise 的状态还是 pending。
这时候,不能立即执行 onFulFilled或者onRejected方法,那我们可以将这两个函数保存起来,利用回调,在真正 resolve 的时候执行这两个函数:
此时即能兼容异步的情况
1.3 .then方法异步
我们看下如下的例子:
我们知道以上的代码会输出 1,2,3,4,而我们目前的代码则会输出1,2,4,3
即 .then 方法是异步而非同步执行,因此3会在4之前打印,因此我们可以这样处理,将 resolve 和 reject 代码变为异步的:
1.4 链式调用
我们知道 Promise 中 .then 出的结果还可以继续调用 .then 方法,Promises/A+ 规范规定 Promise.then 必须返回一个 Promise 对象,因此改写 then 方法:
由于 Promise 的构造函数会立即执行,因此不影响原有的代码
.then返回普通值
考虑以下的例子:
即链式调用中,下一个的 then 会接收上一个 then 中 return 的结果,如果没有 return, 则接收 undefined
.then返回Promise
即在链式调用中,如果上一个 then 返回 Promise,那么下一个 then 会接收上一个 Promise中resolve或者reject的值。
再次进行完善:
至此,一个比较完整的 Promise 就完成.
之后,我们可以添加一些常见的类方法和实例方法
1.5 .catch 方法
.catch 方法接受一个函数作为参数,该函数会在之前的 Promise reject 时执行。
1.6 Promise.resolve()
该方法接收一个值作为参数,返回 resolve 该值的 Promise
1.7 Promise.reject()
同 Promise.resolve(),代码如下:
1.8 Promise.all()
1.9 Promise.race()
2. 相关题
2.1 Promise 的异常捕获
首先回顾下 .catch 和 .then 的源码:
然后我们查看示例一:
当 Promise.reject 之后,返回了一个 rejected 状态的 promise
第一个 then 中,由于没有第二个参数 onRejected 函数,所以默认为 throw Error, 此时又会返回一个 rejected 状态的 promise。
同理第二个 then 也是这样,返回一个 rejected 状态的 promise
在最后的 .catch 中,返回的 rejected 的 promise,执行
promise.then(null, err => { console.log(err) })
即由于 Promise rejected之后,在前面没有被代码处理,导致一次次产生新的 rejected 状态的 Promise。
再看看示例二:
当 Promise.reject 之后,返回了一个 rejected 状态的 promise
第一个 then 中,有第二个参数 onRejected 函数,且该函数没有报错或者返回 rejected 状态的 promise,因此返回一个新的 resolved 状态的 promise。
此时示例代码就可以简化如下:
即是否执行 catch 里的代码,与最初的 Promise.reject 无关。
2.2 如何串行执行 Promise
如下三个返回 Promise 的函数,如何让三个函数中的 Promise 依次执行
要完成以上的需求,我们可以使用 promise 链式调用的方式:
然后转用 reduce,可以写出以下的代码:
但是,以上代码中,如果有一个 promise(比如 promise2) reject了,那么执行链就会断掉:
因此我们还需要捕获 promise 的异常,即使 reject 了也要继续执行之后的代码:
即最终打印了两遍 promise2, 这是为什么呢?
其实以上的代码,还原回正常的代码应该是:
即以上代码有两个问题: 1. 在第一次执行的时候,会执行一遍 promise2, 而此时返回的 Promise 是 reject 状态的, 会进入 .catch 执行第二次,这时候又会再执行一遍 promise2 2. 第二次的 promise 依旧是 reject 状态,所以最终执行的是 6, 而不是 5,虽然没啥问题,但就是觉得怪怪的。
我们可以想到以下的代码,可以处理这个第一个问题:
即依然会有第二个问题。
另外一种解决方案是:
2.3 如果 resolve 一个 Promise, 结果会如何?
面试题,以下代码会打印什么结果?
放到控制台执行就知道了会打印 error, 为什么呢?按照我们的源码,我不管 resolve 的值是什么,只要是 resolve, 就应该执行 onFulFilled 才对。
但是,其实 有个 issue 里讨论过这个:
Q: 为什么 Promise 构造函数里面的 resolve 不需要处理 value 有可能是 thenable 的逻辑?
A: 问的很好。实际上是需要的。ES6 Promise中就对此种情况做出了定义。 Promise/A+标准并没有对构造函数的签名及行为做出定义,只定了then方法的行为。所以Promise/A+的测试中并不包含测试构造函数的用例。
也就是说,我们平常使用到的 Promise 不仅仅实现了 Promise/A+ 的规范,同时对构造函数部分的代码也做了处理。因此我们上面的源码不能反映 ES6 中 Promsie 的结果
于是我找到了 ES6 Promise , 且测试了结果确实是打印 error。
于是我们想到,如果 resolve 中的结果是个 promise,我们应该将它的值作为 new Promise 的值,于是改进代码:
而查看 es6-promise, 确实在 resolve 的时候会有取 value.then 相关的代码(具体逻辑暂不研究):
于是,我又想,是不是在 reject 的时候,也会如此呢?:
结果发现并不是,结果还是打印 error , 因为 es6-promise 中的 reject 并没有进行 value.then 的处理(粗略翻看代码,并没有 value.then 的操作)。
参考资料
yck 前端面试之道
Last updated
Was this helpful?