在上篇文章Promise-Polyfill源码解析(1)详细分析了Promise构造函数部分的源码,本篇我们继续分析剩下的源码。
本篇我们重点分析then方法,让我们回忆下then方法的使用方式:首先这个方法属于每个Promise对象,这说明then方法应该定义在Promise的原型链上;然后这个方法接收两个回调函数,如果Promsie的状态为已完成,则执行第一个回调,状态为被拒绝,则执行第二个回调,这个说明then方法会等待Promise状态改变才会去执行回调;最后then方法可以链式调用,如下:
Promise.resolve().then(function() {
// ...
}, function() {
// ...
}).then(function() {
// ...
}, function() {
// ...
});
了解了以上,我们来看then方法的源码:
Promise.prototype.then = function(onFulfilled, onRejected) {
// @ts-ignore
var prom = new this.constructor(noop);
handle(this, new Handler(onFulfilled, onRejected, prom));
return prom;
};
正如我们所猜想的,then方法定义在Promise的构造函数上,每个Promise对象可以共享该方法。其接收两个参数onFulfilled、onRejected。具体实现也非常简洁,只有三行代码,先来看第一行:
var prom = new this.constructor(noop);
这句代码用new操作符实例化了一个对象,并保存在prom变量中。new操作符的右边一定是个构造函数,this指向当前Promise对象,其constructor属性指向构造函数,所以this.constructor指向Promise构造函数。我们知道,Promise构造函数的参数为一个函数,这里传入了noop,noop是什么?我们找到其定义:
function noop() {}
我们发现noop只是个空函数。再来看最后一行代码:
return prom;
返回了prom对象,也就是说,then方法最后返回了一个Promise对象,这也就是then方法可以链式调用的原因所在!
有个疑问,为什么不直接返回this,而是返回新创建的Promise对象呢?其实是因为Promise的状态改变时单向的,且只能改变一次。
然后重点来看下第二行代码:
handle(this, new Handler(onFulfilled, onRejected, prom));
调用了handle函数,先不管handle做了什么,我们先关注其第二个实参:
new Handler(onFulfilled, onRejected, prom)
其实例化了Handler对象,参数为then方法的两个参数和prom对象,我们来看下其具体实现:
/**
* @constructor
*/
function Handler(onFulfilled, onRejected, promise) {
this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
this.onRejected = typeof onRejected === 'function' ? onRejected : null;
this.promise = promise;
}
Handler构造函数将传入的参数分别赋值给实例对象的onFulfilled、onRejected、promise属性,其中对onFulfilled和onRejected做了处理,若不是函数类型,则赋值为null。这说明,我们传入给then方法的两个参数可以不为函数类型,其内部会调整为null。
明白了第二个参数,我们来看handle函数具体做了什么:
function handle(self, deferred) {
while (self._state === 3) {
self = self._value;
}
if (self._state === 0) {
self._deferreds.push(deferred);
return;
}
self._handled = true;
Promise._immediateFn(function() {
var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
if (cb === null) {
(self._state === 1 ? resolve : reject)(deferred.promise, self._value);
return;
}
var ret;
try {
ret = cb(self._value);
} catch (e) {
reject(deferred.promise, e);
return;
}
resolve(deferred.promise, ret);
});
}
首先是一个while循环:
while (self._state === 3) {
self = self._value;
}
self指的是当前Promise对象,如果self._state的值为3,则将self._value赋值给self。我们在上篇文章分析过,_state属性值为3,则说明_value值为一个Promise对象。那么这个循环的结果就是,直到_value属性值不为Promise对象,为什么要这么处理呢?我们来看下规范是怎么说的:
如果 x 为 Promise,则使promise接收x的状态
- 如果 x 处于pendding,promise需要保持为pendding状态直至x被解决或拒绝
- 如果 x 处于fulFilled,用相同的值执行 promise
- 如果 x 处于rejected,用相同的据因拒绝 promise
总结起来就是,如果_value属性值为Promise对象,则结果取决于嵌套最内层Promise的状态。
接下来是一个条件判断:
if (self._state === 0) {
self._deferreds.push(deferred);
return;
}
如果self._state属性为0,则将deferred压入self._deferreds数组,并结束此次函数调用。其中deferred为传入的Handler实例对象,我们在上篇里分析过,_state属性值为0表示Promise的状态为pendding,我们可以猜测到,状态为pedding,也就是Promise的状态并未改变,then方法不知道要执行哪个回调,所在要先保存。那么为什么是保存在一个数组里,而不是保存在一个变量里,难道有很多个?其实还真可能有很多个,因为then方法可以被多次调用:
image.png
可以看到,每个then方法的回调都被执行了。
再来看下面的代码:
self._handled = true;
上篇文章也分析过,_handled属性用来标记Promise是否被处理,这里将其赋值为true,说明当前Promise对象已经被处理了。
最后来看最后一段代码:
Promise._immediateFn(function() {
...
});
调用了Promise._immediateFn方法,并传入了一个回调函数。先来看Promise._immediateFn的定义:
// Use polyfill for setImmediate for performance gains
Promise._immediateFn =
(typeof setImmediate === 'function' &&
function(fn) {
setImmediate(fn);
}) ||
function(fn) {
setTimeoutFunc(fn, 0);
};
这里判断setImmediate是否是函数类型,成里则赋值为function(fn) { setImmediate(fn) },否则赋值为function(fn) { setTimeoutFunc(fn, 0) },其中setTimeoutFunc是setTimeout的别名:
var setTimeoutFunc = setTimeout;
setImmediate是Node.js里的global对象的属性,而setTimeout是浏览器环境里window对象的属性,所以Promise._immediate是兼容两个环境所做处理的代码。为什么要再包一层闭包呢?应该是兼容参数的数量。
到这我们也明白了,then方法的回调是异步执行,其实更具体是在micro队列中,这里我们就不展开了。
回到Promise._immediateFn的回调参数:
var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
上篇文章分析过,self._state属性值为1表示Promise的状态为已完成,为2表示状态为被决绝。那么这句代码的意思是,根据Promise的状态,将then方法的完成回调或决绝回调赋值给cb变量。
再来看下面的条件判断:
if (cb === null) {
(self._state === 1 ? resolve : reject)(deferred.promise, self._value);
return;
}
cb变量为null,也就是我们传入给then方法的参数不是函数类型,这里会根据Promise的状态执行resolve或reject函数,并结束此次调用。注意传入的参数,deferred.promise和self._value,也就是说,用Promise的值去改变在then方法内创建的Promise对象的状态。总结起来就是,若then方法未传入对应的回调,那么Promise的值会被传递到下一次then方法中:
image.png
再来看最后一段代码:
var ret;
try {
ret = cb(self._value);
} catch (e) {
reject(deferred.promise, e);
return;
}
resolve(deferred.promise, ret);
忽略try..catch,核心是这样的:
var ret = cb(self._value);
resolve(deferred.promise, ret);
将self._value作为参数,调用cb函数,返回值保存在ret变量中,再以ret变量为参数调用resolve函数。这里的意思就是,将cb函数的返回值作为Promise的值传递给下一个then方法:
image.png
当然,若抛出异常,则将原因作为Promise的值,传递给下一个then方法:
reject(deferred.promise, e);
return;
至此,Promise源码的核心部分已经分析完了,我们可以发现,阅读源码可以了解Promise的内部的工作机制,当出现问题时,我们也能快速定位原因。鼓励大家去阅读源码!
当然还有catch、all、race等方法,将在下一篇文章继续分析。
网友评论