promise ployfill 代码分析
promise polyfill 代码分析
一、整体框架介绍
分析 Promise,我们可以从三个阶段入手:
new Promise(fn)
创建一个 promise 实例时p.then(...)
指定 promise 的回调函数时- 异步函数执行到
resolve(vl)
或reject(vl)
时
正常来说,异步函数 fn 的 Promise 会按上述 1→2→3 顺序执行。但是如果 fn 是同步函数的话则会 1→3→2 的顺序。
我们先按正常的异步函数 Promise 来分析一下这三个步骤:
1.1 new Promise(fn)
用 new 操作符创建一个 Promise 实例时,做的操作其实不多,主要是初始化 Promise 实例的一些状态。new 出来的 Promise 实例大概长这个样子:
{
_state: 0,
_value: null,
_deferredState: 0,
_deferreds: null,
}
这四个属性作用都很大,我们来一一介绍一下。
_state
p._state
里存放的是 Promise 实例的状态,它有 4 种取值:
- 0 → 异步 fn 还在执行(pending)
- 1 → 异步 fn 已经成功执行(fulfilled)
- 2 → 异步 fn 执行失败 (rejected)
- 3 → 异步 fn 已经成功执行,但是 resolve 出来的不是一个常量,而是另一个 promise 实例 p2 ,这个时候该 promise 的通过 then 方法指定的回调会被 p2 继承
_value
p._value
里面存储着 promise 当前状态的返回值,具体如下:
-p._value == 0
→ null
p._value == 1
→resolve(vl)
里传递出来的 vlp._value == 2
→reject(res)
里传递出来的 resp._value == 3
→resolve(p2)
时传递出来的另一个 Promise 实例 p2
_deferreds
这个属性其实和 Promise.prototype.then
有很大的关系,但是现在我们还没有介绍到 Promise.prototype.then
,所以我们先这样理解:
p._deferreds
是用来存储调用 p.then(fun1, fun2)
时指定的异步回调的,存储的具体单元是 Handler 实例(这个 Handler 类也是后面再说)。
_deferredState
p._deferredState
这个属性是 p._deferreds
的一个辅助变量,它有三种取值:
- 0 → p 没有调用过
p.then
来指定回调,p._deferreds
为 null - 1 → p 调用过一次
p.then
来指定回调,p._deferreds
为 Handler 实例 - 2 → p 调用过两次
p.then
来指定回调,p._deferreds
为 Handler 实例组成的数组
1.2 Promise.prototype.then
好的,现在我们说说 Promise.prototype.then
。废话不多说,先上源码:
Promise.prototype.then = function(onFulfilled, onRejected) {
/**
* this: promise 实例
* onFulfilled: 开发者指定的成功回调
* onRejected: 开发者指定的失败后回调
*/
...
// noop 是一个空函数,这里是创建了空函数的 Promise 实例 res
var res = new Promise(noop);
// 将两个回调和res封装到一个 Handler 实例,并调用 handle 将调用 then 方法的 promise 实例与 Handler 实例关联起来
handle(this, new Handler(onFulfilled, onRejected, res));
// 将 res 返回,此时 res._state 为 0
return res;
};
可以看到,它的代码很简单,因为大部分的操作都隐藏在 handle 函数里了。
handle 函数是用来干嘛的呢?如上面注释所言:将两个回调和res封装到一个 Handler 实例,并调用 handle 将调用 then 方法的 promise 实例与 Handler 实例关联起来。(这只是handle 函数的一部分功能,handle 还有着其他更重要的作用,后面再说)。
这个 Handler 实例长这个样子:
let deferred = {
onFulfilled: onFulfilled, // 异步成功回调
onRejected: onRejected, // 异步失败回调
promise: res // p.then() 返回的 Promise 实例
}
那么怎么产生关联的,其实也简单:
p._deferredState = 1; // 标记 p 是有回调的
p._deferreds = deferred; // 通过 p._deferreds 与 Handler 实例 deferred 关联起来
此时,Promise 实例是长这个样子了:
{
_state: 0,
_value: null,
_deferredState: 1,
_deferreds: deferred,
}
1.3 resolve(vl) 与 reject(res)
一个 Promise 创建好,又调用 then 指定好回调了,接下来就是等待异步函数执行到 resolve(vl) 或 reject(res) 了。
reject(res)
先说说 reject(res) 吧,异步函数 fn 执行到 reject(res) 证明 fn 执行失败了。reject(res) 会做这几件事:
p._state = 2
, 更新 p 的状态码为 2,标记着 fn 执行失败p._value = res
,将通过reject(res)
传递出来的 res 赋值到p._value
,作为这个 Promise 实例最后的值- 执行
self._deferreds
里面的存储的失败回调
resolve
异步函数 fn 执行到 resolve(vl) ,证明 fn 执行成功了。resolve(vl) 会做这几件事:
- vl 为 promise 实例:
1.1p._state = 3
1.2p._value = vl
1.3 将self._deferreds
里面的回调扔给 vl,作为 vl 的回调 - vl 为 拥有 then 方法的对象:递归,将
vl.then
看作 p 新的异步函数去执行 - 其他:
3.1p._state = 1
3.2p._value = vl
3.3 执行self._deferreds
里面的存储的成功回调
handle 函数
其实无论是 reject(res) 还是 resolve(vl),它们执行到最后,都会调用同一个函数 handle(self, self._defferreds)
来处理自己的回调。
是的,你没看错,还是 handle
这个函数,前面我们有说过,调用 then 方法的时候也会执行 handle 函数来将回调函数存储到 p._defferreds
里面 。
所以,handle 函数的作用可以分为两大块:
- 存储 Promise 实例的回调函数
- 处理 Promise 实例的回调函数
function handle(self, deferred) {
// self: promise 实例
// deferred: Handler 实例
...
if (self._state === 0) {
// 异步函数还没有完成,将 deferred 存储到 self._defferreds 里
...
return;
}
// 异步函数已经执行完毕,执行 handleResolved 去处理 deferred 里的回调函数
handleResolved(self, deferred);
}
关于存储的过程我们在介绍 _deferreds
和_deferredState
两个内部属性的时候已经大概说过了,这里不再赘述。
我们现在主要看看 handle 是怎么调用 handleResolved 处理回调函数的。
function handleResolved(self, deferred) {
// self: promise 实例
// deferred: Handler 实例
asap(function() {
var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
// 根据状态码获取对应回调
if (cb === null) {
// 没有指定回调
...
return;
}
// 有指定回调,执行回调函数,并将 promise 结果传入作为回调的参数
var ret = tryCallOne(cb, self._value);
...
});
}
这个 asap 是 Promise 执行异步回调的一个工具库,用来兼容 Node 和浏览器环境,将回调函数以高优先级任务来执行(下一个事件循环之前),即把任务放在微任务队列中执行。
看到这里我们应该明白了,resolve / reject 传递出来的值会存储在 p._value
里,然后等着 self._deferreds
里回调函数将其作为参数来调用。
以上,就是 Promise 的大致实现原理。
二、Promise 的状态传递
分析上面的整体流程的时候,有一个点我们没有说得特别清楚,就是执行 resolve(vl) 的时候 vl 是一个 Promise 实例这种情况。
举个例子:
new Promise((resolve, reject) => {
setTimeout(()=>{
let a = new Promise(resolve => resolve(1))
resolve(a);
}, 1000)
})
.then(res=>console.log(res)) // 1
关于这种情况,我们一步步分析:
- 首先,新建了一个 Promise 对象 p
- 执行了 p.then,初始化了 p._deferreds
- 1 秒后,执行 resolve(a),因为 a 是一个 promise 实例,所以 p._state = 3,p._value = a
- 执行 handle(p, p._deferreds ) 处理回调
在这之前,思路都是清晰的,关键还是在于 handle 函数。其实前面介绍 handle 的时候为了精简,写少了处理这种情况的关键一步:
function handle(self, deferred) {
while (self._state === 3) {
self = self._value;
}
...
}
如代码所示,对于 p._state 为 3 的情况,handle 函数会通过 while 循环拿到 a。
然后 handle 下面全部的处理,都是针对 a 和 p 的回调函数 deferred。也就是说,虽然 p 的异步函数已经成功执行了,但是不会立即执行 p 的成功回调,而是得看 a 的执行结果,如果 a 执行成功才会执行 p 的成功回调,否则会执行 p 的失败回调。这就是所谓的 a 决定了 p 的状态。
另外,then 回调返回一个 Promise 实例时,也是一样:
Promise.resolve(1)
.then(res=>Promise.resolve(2))
.then(res=>console.log(res)); // 2
三、Promise 的链式调用
我们都知道,Promise 之所以成功,有一个关键因素就是它可以通过 then 进行链式调用,而这个链式调用的原理是怎么样的呢,下面我们就来分析一下。
链式调用的关键,在于 handleResolved 函数,所以我们来看看它的具体实现。
function handleResolved(self, deferred) {
// self: promise 实例
// deferred: Handler 实例
asap(function() {
var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
if (cb === null) { // 没有指定回调
if (self._state === 1) { // 将 promise 结果传递给 then 产生的 promise
resolve(deferred.promise, self._value);
} else {
reject(deferred.promise, self._value);
}
return;
}
// 指定 promise 结果 self._value 作为回调 cb 的参数,并执行
var ret = tryCallOne(cb, self._value);
if (ret === IS_ERROR) { // 将回调返回值传递给 then 产生的新 promise
reject(deferred.promise, LAST_ERROR);
} else {
resolve(deferred.promise, ret);
}
});
}
还是看一个例子:
let a = Promise.resolve(1); // a._value = 1
let b = a.then(); // b._value = 1
let c = b.then(res=>res+1); // res = 1, c._value = 2
let d = c.then(res=>console.log(res)); // res = 2, d._value = undefined
调用 p.then() 指定回调时,回调函数的参数一定是 p 的值。
返回的新 promise 实例 res 一定会被处理,只不过处理的方式和参数有区别:
- p.then 指定的成功回调为 null,p._state 为 1,res 被 resolve(res, p._value)
- p.then 指定的失败回调为 null,p._state 为 2,res 被 reject(res, p._value)
- p.then 指定的成功/失败回调正常执行,res 会被 resolve(res, 回调返回值)
- p.then 指定的成功/失败回调执行出错,res 会被 reject(res, 错误)
四、ES6 Promise 特性
4.1 Promise.prototype.catch
ES6 里 Promise 的 catch 是 then 指定错误回调的一种语法糖,实现代码也比较简单:
Promise.prototype['catch'] = function (onRejected) {
return this.then(null, onRejected);
};
说白了,还是 then,不过没有指定成功回调。
这阵子做有什么好处?它最大的作用在于能够通过特定的调用形式来将promise链式调用里的错误全部捕获。
前面我们说过,在 handleResolved 函数里,如果异步函数执行完毕后,如果 then 没有指定对应的回调函数,那么异步函数的返回值会被 “冒泡” 到下一个 then 指定的回调里。
所以,链式调用多个 promise 的时候,我们可以用 then 来只指定成功回调,然后在链式调用的末尾放一个 catch 捕获捕获。一旦链式调用遇到了错误,因为没有指定错误回调,这个错误会不断地“冒泡”到下一个 then,最后被末尾的 catch 捕获。
Promise.resolve(1)
.then()
.then(data=>{throw Error('1')})
.then()
.catch(err=>console.log(err));
这种写法也是阮一峰的《ES6标准入门》里推荐的写法。
那么,这个错误是怎么被捕获到并且通过 reject 传递处理的呢?
其实,promise 在执行异步函数、回调函数的时候,都采用了 try 和 ctach(比如 handleResolved 函数里通过 tryCallOne 来执行回调函数时),一旦捕获到错误,就会将错误通过 reject 传递出来。
4.2 Promise.resolve
这个方法用来生成一个 fullfilled 的 Promise 实例,实例的值是该方法的参数。关键函数是 valuePromise
function valuePromise(value) {
var p = new Promise(Promise._noop);
p._state = 1;
p._value = value;
return p;
}
Promise.resolve 也存在特殊情况:
- 参数是 Promise 实例的,直接返回参数
- 参数是具有 then 方法的对象时,返回
new Promise(then)
- 对于第2种情况,如果 then 方法执行错误,会返回
Promise.reject(错误)
4.3 Promise.reject
生成一个 rejected 的 Promise 实例,实例的值是该方法的参数。源码如下:
Promise.reject = function (value) {
return new Promise(function (resolve, reject) {
reject(value);
});
};
4.2 Promise.all
假设执行如下代码:
let p = Promise.all([p1, p2, p3]);
p 的状态由 p1、p2、p3 决定,分两种情况:
- 只有 p1、p2、p3 都变为 fulfilled 时,p 的状态才会变为 fulfilled。此时 p1、p2、p3 的返回值组成一个数组,传递给 p 的回调函数。
- 只要 p1、p2、p3 有一个被 rejected,p 的状态就会变成 rejected。此时,第一个被 rejected 的实例返回值会传递给 p 的回调函数。
看看代码实现:
Promise.all = function (arr) {
var args = iterableToArray(arr); // 将 arr 分解为普通数组
return new Promise(function (resolve, reject) {
if (args.length === 0) return resolve([]);
var remaining = args.length;
function res(i, val) {
if (val && (typeof val === 'object' || typeof val === 'function')) {
if (val instanceof Promise && val.then === Promise.prototype.then) {
// val 是 Promise 实例
while (val._state === 3) {
val = val._value;
}
if (val._state === 1) return res(i, val._value);
if (val._state === 2) reject(val._value);
val.then(function (val) {
res(i, val);
}, reject); // 一旦有错,立刻 reject
return;
} else {
// val 是拥有 then 方法的对象
var then = val.then;
if (typeof then === 'function') {
var p = new Promise(then.bind(val));
p.then(function (val) {
res(i, val);
}, reject);
return;
}
}
}
// val 是普通值,也就是 promise 返回值时
args[i] = val;
if (--remaining === 0) {
resolve(args);
}
}
for (var i = 0; i < args.length; i++) {
res(i, args[i]); // 用 i 记住 promise 原本下标,方便 promise 返回值插回原位
}
});
};
4.3 Promise.race
Promise.race 同样是将多个 Promise 实例包装为一个新的 Promise 实例。
let p = Promise.all([p1, p2, p3]);
不同的是,只要 p1、p2、p3 其中有一个状态改变,p 的状态也跟着改变。率先改变的 Promise 实例的返回值就会传递给 p 的回调函数。
代码实现:
Promise.race = function (values) {
return new Promise(function (resolve, reject) {
iterableToArray(values).forEach(function(value){
Promise.resolve(value).then(resolve, reject);
});
});
};