Promise源码解析
纸上得来终觉浅,绝知此事要躬行。之前只是很浅显的知道Promise的用法,也大概猜测到它的内部是如何实现的。但是总是有一种不深究一下就不踏实的感觉。于是从npm上获得早期的Promise源代码,拿过来读一读,做一做笔记。
Promise的源码写的非常的巧妙,凭空阅读会陷入入其中无法自拔。
简单的Promise用法
一个简单的Promise用法如下:
const promiseObj = new Promise(function (resolve, reject) {// 代码执行resolve('value');//orreject('error');
});
promiseObj.then(function (value) {// do sth...
}, function (error) {// deal excption...
});
promiseObj.catch(function (error) { // catch excption...
});
我们就从这个简单的例子开始分析Promise的执行是怎么样的。
Promise的源代码位于:https://github.com/stefanpenner/es6-promise.git ,这里采用的版本为0.1.0版本。
我们从Promise的构造函数开始说起:
function Promise(resolver) {... // 省略的部分为必要的校验this._subscribers = [];invokeResolver(resolver, this);
}
_subscribers对象用于存储这个Promise实例所对应的观察者。紧接着执行invokeResolver();
function invokeResolver(resolver, promise) {function resolvePromise(value) {resolve(promise, value); // p2, p1}function rejectPromise(reason) {reject(promise, reason);}try {resolver(resolvePromise, rejectPromise);} catch(e) {rejectPromise(e);}
}
invokeResolver为关键的一环。在这里,构造Promise对象时所传入的方法会被执行,并将执行方法所需的两个回调参数resolvePromise和rejectPromise传了进去,方便业务代码通知Promise对象执行结果。
如果我们的代码很简单的执行了一行代码,例如:
resolve('Hello');
那么resolvePromise会紧接着调用resolve方法。我们进入resolve方法一探究竟:
function resolve(promise, value) { // promise为新构造的Promise对象,value = 'Hello'.if (promise === value) {fulfill(promise, value);} else if (!handleThenable(promise, value)) {fulfill(promise, value);}
}
我们现在的逻辑会直接进入fulfill方法:
function fulfill(promise, value) {// promise为新构造的Promise对象,value = 'Hello'.if (promise._state !== PENDING) { return; } // 当前不满足,默认为PENDING状态promise._state = SEALED;// promise._state = SEALED promise._detail = value;// promise._detail = 'Hello' config.async(publishFulfillment, promise);
}
到这里,将结果值赋值给了Promise对象。也就是说Promise对象保留了计算后的结果值’Hello’。然后我们看一下config.async()是个什么鬼:
function asap(callback, arg) {var length = queue.push([callback, arg]);if (length === 1) {// If length is 1, that means that we need to schedule an async flush.// If additional callbacks are queued before the queue is flushed, they// will be processed by this flush that we are scheduling.scheduleFlush();}
}
上面这段代码位于lib/promise/asap.js
中。它主要用来将任务callback加入下一个时间片中。执行到这里会将[publishFulfillment, promise]
组成一个数组放在队列中,然后紧接着根据平台进行任务刷新,也就是执行scheduleFlush();
不过,不管scheduleFlush方法再怎么快,它也是被放在了所有事件最后才执行。所以接下来执行的代码是主线程继续执行的代码:
promiseObj.then(function (value) {// do sth...
}, function (error) {// deal excption...
});
promiseObj.catch(function (error) { // catch excption...
});
到这里我们需要看看Promise.then方法是怎么执行的:
then: function(onFulfillment, onRejection) {var promise = this;var thenPromise = new this.constructor(function() {});if (this._state) {var callbacks = arguments;config.async(function invokePromiseCallback() {invokeCallback(promise._state, thenPromise, callbacks[promise._state - 1], promise._detail);});} else {subscribe(this, thenPromise, onFulfillment, onRejection);}return thenPromise;},
在调用then方法时,产生了一个新的Promise对象thenPromise,它会参与后面的任务执行。现在我们根据上下文进入subscribe方法:
function subscribe(parent, child, onFulfillment, onRejection) { // parent = customPromiseObject, child = thenPromise, onFulfillment = 成功回调,onRejection = 失败回调var subscribers = parent._subscribers;var length = subscribers.length;subscribers[length] = child;subscribers[length + FULFILLED] = onFulfillment;subscribers[length + REJECTED] = onRejection;
}
subscribers初始化是一个空数组,所以在这里执行完毕后,将会是以下效果:
subscribers[0] = thenPromise;subscribers[1] = 成功回调; subscribers[2] = 失败回调;promise._subscribers = subscribers;
由于我们的示例代码使用了catch,所以贴一下catch方法的源代码:
'catch': function(onRejection) {return this.then(null, onRejection);}
所以在执行完then方法以及catch方法之后,promise._ subscribers内部如下:
subscribers[0] = thenPromise;subscribers[1] = 成功回调; subscribers[2] = 失败回调; subscribers[3] = thenPromise2;subscribers[4] = null; subscribers[5] = catch回调;
到这里,我们的同步事件就执行完了。接下来开始分析异步事件。我们回到lib/promise/asap.js文件。
在Promise早期的源代码中,对Promise的运行平台做了区分。我们这里对这块细节不做深究,但不管是哪个平台,最终还是会执行到flush方法。这里需要注意:在执行flush方法时,它已经在所有事件执行之后了。这里的所有事件指的是已经进入主线程队列的事件,也就是刚刚执行完成的同步事件。
function flush() {for (var i = 0; i < queue.length; i++) {var tuple = queue[i];var callback = tuple[0], arg = tuple[1];callback(arg);}queue = [];
}
flush方法在这里会回调刚刚传入的方法publishFulfillment,参数为那个promise对象。我们确认一下publishFulfillment方法的细节:
function publishFulfillment(promise) {publish(promise, promise._state = FULFILLED);
}
它这里很简单,直接调用了publish方法。不过在这里,promise对象的状态再一次被改变了。从PENDING -> SEALED -> FULFILLED。我们进入publish:
function publish(promise, settled) { // promise = new Promise, settled = FULFILLEDvar child, callback, subscribers = promise._subscribers, detail = promise._detail;for (var i = 0; i < subscribers.length; i += 3) { child = subscribers[i];callback = subscribers[i + settled]; invokeCallback(settled, child, callback, detail); }promise._subscribers = null;
}
上面的代码开始Promise对象的观察者进行通知。注意这里的for循环只循环了两次。第一次循环:
child == thenPromise;callback == 成功回调;
我们先来看正常的执行,这里直接调用了invokeCallback方法:
function invokeCallback(settled, promise, callback, detail) { settled = FULFILLED, promise = thenPromise, callback = 成功回调, detail = 'Hello'// 检测callback是否是一个方法var hasCallback = isFunction(callback),value, error, succeeded, failed;// 如果是方法,则执行if (hasCallback) {try {value = callback(detail); succeeded = true;} catch(e) {failed = true;error = e;}} else {value = detail;succeeded = true;}if (handleThenable(promise, value)) {return;} else if (hasCallback && succeeded) {resolve(promise, value);} else if (failed) {reject(promise, error);} else if (settled === FULFILLED) {resolve(promise, value);} else if (settled === REJECTED) {reject(promise, value);}
}
咱们的示例比较简单,在检测回调方法是一个方法之后,就直接调用了。我们的示例也没有返回值。所以直接走进了hasCallback && succeeded的判断中,然后进入resolve方法。
function resolve(promise, value) { // promise = thenPromise, value = undefinedif (promise === value) {fulfill(promise, value);} else if (!handleThenable(promise, value)) {fulfill(promise, value);}
}
根据上下文,这里进入了fulfill方法:
function fulfill(promise, value) {// promise = thenPromise, value = null if (promise._state !== PENDING) { return; }promise._state = SEALED;// thenPromise._state = SEALED promise._detail = value;// thenPromise._detail = null config.async(publishFulfillment, promise);
}
到这里是不是似曾相识?没错,我们在上面已经见过上面两个方法。不过在这里是要通过flush回调执行thenPromise对象。然后根据上面提到的逻辑,最后thenPromise将会进入publish:
function publish(promise, settled) { // promise = thenPromise, settled = FULFILLED var child, callback, subscribers = promise._subscribers, detail = promise._detail;for (var i = 0; i < subscribers.length; i += 3) { child = subscribers[i];callback = subscribers[i + settled]; invokeCallback(settled, child, callback, detail); }promise._subscribers = null;
}
不过thenPromise对象是一个空对象,它的_subscribers属性是一个空数组。所以这里自然执行完毕。
不过到这里也才是thenPromise执行完毕,我们自己构造的Promise对象呢?它才准备进行第二次for循环:
child == thenPromise2;callback == null;
然后根据上下文,在执行到invokeCallback方法,不过最终它的命运匹配到了settled === FULFILLED的条件,进入了resolve方法:
function resolve(promise, value) { // promise = new Promise, value = undefinedif (promise === value) {fulfill(promise, value);} else if (!handleThenable(promise, value)) {fulfill(promise, value);}
}
再进入fulfill方法:
function fulfill(promise, value) {if (promise._state !== PENDING) { return; }promise._state = SEALED;promise._detail = value;config.async(publishFulfillment, promise);
}
不过执行到这里,promise的_state已经为SEALED了,不等于PENDING,所以Promise也就自然终结了。
至此,一个普通的Promise执行终结。