promise 浏览器实现的源码_Promise 源码分析

Promise 源码分析

一、整体框架介绍

分析 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) 里传递出来的 vl

p._value == 2 → reject(res)里传递出来的 res

p._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.1 p._state = 3

1.2 p._value = vl

1.3 将 self._deferreds 里面的回调扔给 vl,作为 vl 的回调

vl 为 拥有 then 方法的对象:递归,将 vl.then 看作 p 新的异步函数去执行

其他:

3.1 p._state = 1

3.2 p._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);

});

});

};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值