前言
Promise是异步编程的一种方案,ES6规范中将其写入规范标准中,统一了用法。
考虑到浏览器的兼容性,Vue项目中使用promise,就具体阅读promise源码,看看内部的具体实现。
具体分析
通过具体实例来阅读promise源码的实现,实例如下:
new Promise(function (resolve, reject) {
get('http://www.google.com', function (err, res) {
if (err) reject(err);
else resolve(res);
})
}).then(function(res) {
// 相关处理
});
Promise库的GitHub地址,实际上在源码中只需要关注下面几个文件即可(这几个文件实现了经常使用的API):
- core.js
- es6-extensions.js
- finally.js
core.js
核心文件,核心功能是:
提供Promise构造函数的定义以及resolve、reject的具体处理
ES6 Promises规范中,Promise代表异步操作,其状态有三个:
pending:进行中
fulfilled:已成功
rejected: 已失败
因为promise中需要关注的代码量不是很多,就结合具体的代码进行。
Promise构造函数
Promise构造函数中的处理:
function Promise(fn) {
// 防止Promise()作为函数调用
// 浏览器中作为函数此处this === window,并没有起到要达到的效果
if (typeof this !== 'object') {
throw new TypeError('Promises must be constructed via new');
}
// 必须要传递函数
if (typeof fn !== 'function') {
throw new TypeError('Promise constructor\'s argument is not a function');
}
// Promise内部维护的状态,默认为0即为pending,进行中
this._deferredState = 0;
this._state = 0;
this._value = null;
this._deferreds = null;
if (fn === noop) return;
doResolve(fn, this);
}
doResolve
从doResolve函数可知,内部实际上是调用tryCallTwo函数来处理,而tryCallTwo函数的处理实际上就是调用Promise中传递的fn函数,并将resolve和reject的回调函数传递到fn中,源码如下:
function tryCallTwo(fn, a, b) {
try {
fn(a, b);
} catch (ex) {
Last_ERROR = ex;
return IS_ERROR;
}
}
这里a就是表示处理resolve状态的函数,相对应的b就是处理rejected状态的函数
resolve
从上面逻辑中可知当执行resolve时,内部会调用已定义好的resolve函数来处相关逻辑。
这里需要的点在getThen函数以及then结果的判断,处理了三个情况:
- getThen执行错误的处理
- then是Promise对象的处理
- then是函数的处理
为什么要这么处理这几种情况,就需要去看getThen具体实现的功能,通过源码可知:
function getThen(obj) {
try {
return obj.then;
} catch (ex) {
LAST_ERROR = ex;
return IS_ERROR;
}
}
通过上面的逻辑处理可知:
resolve这边实际上就是处理newValue,即执行了new Promise后之后传递给resolve的参数问题
主要的判断参数是否存在并且是否是object或function,如果是object或function,则会调用getThen获取then进行下面的判断:
- then是否等于IS_ERROR,即getThen是否执行出错
- newValue是否是Promise对象
- then是否是函数
实际上上面的判断都是处理newValue中是否存在then函数的问题。
只要newValue中不存在then函数,就会设置_state和_value,并调用finale函数。
reject函数
reject中处理逻辑就是设置state和value值,调用finale函数。
state设置为2,表示rejected状态
finale函数
finale函数式实际上是处理fulfilled状态和rejected,分别调用handle来处理相关逻辑。
当你使用new Promise实际上_deferredStat默认为0,而且在之前的处理逻辑中并没有相关处理,因为Promise中是异步操作实际上这边是在then函数中处理。
then函数
Promise.prototype.then = function(onFulfilled, onRejected) {
// 处理非Promise构造函数创建的情况,例如prototype继承未修改constructor时
if (this.constructor !== Promise) {
return safeThen(this, onFulfilled, onRejected);
}
var res = new Promise(noop);
handle(this, new Handler(onFulfilled, onRejected, res));
return res;
};
从上面可知链式调用的实现以及then函数内部的主要处理是handle函数。
首先看看Handler构造函数的作用,实际上是then函数内部会构建promise对象的链表结构,Handler的属性:
function Handler(onFulfilled, onRejected, promise){
// then函数的resolve函数
this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
// then函数的reject函数
this.onRejected = typeof onRejected === 'function' ? onRejected : null;
// 下一个promise对象,实际上就是then函数返回的
this.promise = promise;
}
而handle中的处理逻辑主要点如下:
判断上一个Promise中state,根据状态设置_deferredState和_deferreds的值
根据上一个Promise中state不为0,就会执行handleResolved
实际上handle就是判断上一个Promise中的异步操作是否执行了,没有执行就等待。
实际上上一个promise执行了,就会改变_state的值,而then函数根据_state的值来做相应的判断
而在handleResolved函数中实际上会调用resolve或reject重复new Promise中一部分动作,这里就需要通过实例去整体梳理这样好理解些。
根据实例梳理整个过程如下:
- new Promise(fn)构建promise实例对象,会立即执行fn函数
- fn函数式get请求,这是一个异步操作,而这个异步操作的处理逻辑中会主动触发resolve或reject,所以new Promise创建的promise会等待主动触发resolve或reject
- Promise等待,但是这边的同步代码then函数就执行了,调用Promise.prototype.then暴露的API
- then函数中就构建另一个Promise,创建Hander对象形成链式,并执行handle函数
- handle函数执行就要判断上一个Promise实例的_state(实例这里是new Promise创建的),因为上一个Promise执行异步操作的,_state为0
- _state为0,就设置了_deferredState为1,然后等待上一个Promise执行完异步操作
当实例中new Promise中的get请求成功后,就会触发resolve,此时:
- 会调用resolve函数,resolve函数中则会判断传递进来的数据是否是对象或函数(即处理存在then函数的情况)
- 如果不存在then函数的情况下,就设置_state以及_value值,并调用finale函数
- finale函数中就判断_deferredState值,在上一个Promise等待异步操作中then函数就设置了该值为1,则会调用handle函数
- 实际上调用handle就是去执行handleResolved,执行then函数传递进来的resolve函数,即使用asap来执行resolve函数(如果此时resolve是一个异步操作,实际上就是重复new Promise中主动触发resolve的过程),注意此时是另一个Promise对象了
- 此时resolve的处理就是将上一个Promise传递进来的value值设置为下一个Promise的value值,并将当前的Promise的state状态置为1
- 此时会执行finale,实际上这个函数在此时没有执行任何逻辑,因为此时_state为1,即使再接then函数,也不会设置_deferredState了,而是直接执行handleResolved,因为此步实际上是被asap主动执行了
如此反复上面的过程。
以当时实例promise整个逻辑处理如下: