Promise源码解析
Posted Sahadev_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Promise源码解析相关的知识,希望对你有一定的参考价值。
Promise源码解析
纸上得来终觉浅,绝知此事要躬行。之前只是很浅显的知道Promise的用法,也大概猜测到它的内部是如何实现的。但是总是有一种不深究一下就不踏实的感觉。于是从npm上获得早期的Promise源代码,拿过来读一读,做一做笔记。
Promise的源码写的非常的巧妙,凭空阅读会陷入入其中无法自拔。
简单的Promise用法
一个简单的Promise用法如下:
const promiseObj = new Promise(function (resolve, reject)
// 代码执行
resolve('value');
//or
reject('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 = 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;
上面的代码开始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 = undefined
if (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 = undefined
if (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执行终结。
以上是关于Promise源码解析的主要内容,如果未能解决你的问题,请参考以下文章