es6-promise源代码重点难点分析

  • 摘要

vue和axios都可以使用es6-promise来实现f1().then(f2).then(f3)这样的连写形式,es6-promise其实现代浏览器已经支持,无需加载外部文件。由于promise写法明显由于传统写法,已经越来越被高级程序采用,不懂promise就没法看高级程序。

  • es6-promise源代码重点难点分析

本文以axios中的http request源代码为例来分析es6-promise源代码中最复杂深奥难懂的环节,当异步过程嵌套时,代码还是很复杂的,有点超出想象,如果用ajax来实现,还真不太好写。

通常用promise写代码是这样写的,比如:

function show(){

return new Promise((resolve) => {

bus.$on('optionClickedEvent', (data) => {

resolve(data.optionIndex) //resolve的目的是要执行then(fn)

this._hide()

})

})

show().then(function(index){

});

这就是一个异步过程完成之后就执行下一个callback回调并传递参数,这是典型的最简单的写法。

首先来看promise构造函数代码:

function Promise(resolver) {

initializePromise(this, resolver)

function initializePromise(promise, resolver) {

try {

resolver(function resolvePromise(value) {

_resolve(promise, value);

}, function rejectPromise(reason) {

_reject(promise, reason);

调promise时传递一个resolve方法,它会执行resolve方法,传递两个fn,resolve方法是绑定一个事件,事件触发handler函数执行,

handler函数调用fn,传递事件数据,fn再调用内部_resolve方法,继续传递数据value(data.optionIndex)。

function _resolve(promise, value) {  //这个就是es6-promise提供的resolve()方法

if (promise === value) {

_reject(promise, selfFulfillment());

} else if (objectOrFunction(value)) {

handleMaybeThenable(promise, value, getThen(value));

} else {

fulfill(promise, value);

}

function fulfill(promise, value) {

promise._result = value; //传递的数据保存在promise实例

asap(publish, promise);

}

resolve调用asap:

var asap = function asap(callback, arg) {

queue[len] = callback; //传递的方法保存在queue

queue[len + 1] = arg; //promise实例保存在queue,里面有传递的数据value

len += 2;

if (len === 2) {

if (customSchedulerFn) {

customSchedulerFn(flush);

} else {

scheduleFlush();

异步延迟方法有以下几种:

if (isNode) {  //debug看是false

scheduleFlush = useNextTick();

} else if (BrowserMutationObserver) { //debug看有此方法,类似setTimeout,是异步延迟调度

scheduleFlush = useMutationObserver(); //执行seMutationObserver()会返回一个方法

} else if (isWorker) { //debug看是false

scheduleFlush = useMessageChannel();

} else if (browserWindow === undefined && typeof require === 'function') { //debug看都有

scheduleFlush = attemptVertx();

} else {

scheduleFlush = useSetTimeout();  //就是setTimeout方法

}

function useSetTimeout() {

var globalSetTimeout = setTimeout;

return function () {

return globalSetTimeout(flush, 1);

};

function useMutationObserver() {

var iterations = 0;

var observer = new BrowserMutationObserver(flush); //flush就是callback,用observer调度执行

var node = document.createTextNode('');

observer.observe(node, { characterData: true }); //告诉observer观察属性

return function () {  //这就是scheduleFlush方法

node.data = iterations = ++iterations % 2; //人为修改属性触发observer执行callback

};

}

var queue = new Array(1000);

function flush() {  //让observer异步调度执行的callback方法

for (var i = 0; i < len; i += 2) {

var callback = queue[i];

var arg = queue[i + 1];

callback(arg);  //执行队列里面的方法,参数也从队列里面取,就是publish(promise),传递的数据已经保存在promise实例中

queue[i] = undefined;

queue[i + 1] = undefined;

}

len = 0;

}

执行scheduleFlush方法就是修改属性触发observer调度执行callback,相关数据对象之前已经准备好了。

另外一种写法是:new MutationObserver(callback);

所以异步调度执行除了setTimeout之外,还有observer,意思是一样的,但内部实现机制不同,setTimeout是延迟机制,

observer是DOM元素变化事件触发机制,一般用不着observer,因为一般都是数据变化要同步更新到DOM,而不是DOM有变化

要同步更新到数据,DOM一般不会主动变化,DOM的变化一般都是数据变化同步更新过去的。

再回头看传递给asap存储在queue中要调度执行的callback方法如下:

function publish(promise) {

var subscribers = promise._subscribers;

var settled = promise._state;

if (subscribers.length === 0) {

return;

}

var child = undefined,

callback = undefined,

detail = promise._result;  //_result就是执行resolve()时传递的数据(保存在promise实例中)

for (var i = 0; i < subscribers.length; i += 3) {

child = subscribers[i];

callback = subscribers[i + settled];

if (child) {

invokeCallback(settled, child, callback, detail);

} else {

callback(detail);  //这是执行then(handler)方法并且传递数据,数据是之前保存在promise实例中的

}

}

promise._subscribers.length = 0;

}

是从promise实例中取subscribers[],再从中取数据方法执行,由于执行resolve就是为了执行then(fn),因此执行then(fn)

时会调用subscribe方法把fn存储在subscribers[]中,subscribers[]相当于events[],存储handler。

下面看subscribers[]是如何创建的;

function then(onFulfillment, onRejection) { // 传入f1/f2两个handler

var parent = this;

var child = new this.constructor(noop);

subscribe(parent, child, onFulfillment, onRejection); //调subscribe存储handler。

return child;

可见then会返回一个promise实例,因此可以连写比如show().then(fn).then(fn),因为可以层层嵌套,parent就是then所在的

promise实例,child是返回的promise实例,也就是下一级then所在的promise实例。

function subscribe(parent, child, onFulfillment, onRejection) {

var _subscribers = parent._subscribers;

var length = _subscribers.length;

parent._onerror = null;

_subscribers[length] = child;

_subscribers[length + FULFILLED] = onFulfillment;

_subscribers[length + REJECTED] = onRejection;

if (length === 0 && parent._state) {

asap(publish, parent);

}

}

可见会把handler存储在then所在的promis实例中的_subscribers[]中,事件订阅者与handler是一类意思。

可见promise就是形式上写了一个事件机制,实际上几乎就是顺序执行,show() -> then(handler) -> 事件触发 -> resolve -> handler 应用代码绑定了一个事件,事件触发resolve执行。

如果show()是一个axios.get过程,那么事件就是http响应事件,handler就是http回调。

axios.get().then(function(res){

//http request有响应有返回

},function(){

//http request无响应/网络异常

});

axios.get方法:

utils.forEach(['delete', 'get', 'head'], function forEachMethodNoData(method) {

Axios.prototype[method] = function(url, config) {

return this.request(utils.merge(config || {}, {

method: method,

url: url

}));

};

Axios.prototype.request = function request(config) {

var chain = [dispatchRequest, undefined];

var promise = Promise.resolve(config); //相当于new promise实例而且会执行resolve传递config数据给dispatchrequest

while (chain.length) {

promise = promise.then(chain.shift(), chain.shift()); //then(fn)会立即执行

}

return promise;  //只有resolve这个promise才能传递response数据到axios.get.then(callback)

};

chain[0][1]是request拦截函数,[2]是dispatchrequest,[3][4]是response拦截函数。

每次axios.get请求都会执行一遍这段代码,把chain里面的handler都执行一遍,其中有dispatchrequest,因此会执行

http request过程,promise.then会反复执行,每次执行都会返回一个promise实例,最后一次执行时返回的promise实例做为

axios.get.then的promise实例,那么http request过程如何resolve这个promise实例,执行then()回调函数?

function dispatchRequest(config) {

return adapter(config).then(function onAdapterResolution(response) {

return response; //执行handler返回response数据如何返回到axios.get.then(fn)?

}, function onAdapterRejection(reason) {

return Promise.reject(reason);

});

function xhrAdapter(config) {

return new Promise(function dispatchXhrRequest(resolve, reject) {

request[loadEvent] = function handleLoad() {

settle(resolve, reject, response); //http request请求响应返回之后执行settle

function settle(resolve, reject, response) {

var validateStatus = response.config.validateStatus;

if (!response.status || !validateStatus || validateStatus(response.status)) {

resolve(response);  //resolve new promise实例,传递response data

} else {

then把handler存储起来,resolve执行存储的handler,并传递数据,问题是执行handler返回response数据有何用?

实际上是promise嵌套写法:

Promise.resolve(config).then(function dispatchrequest(config){

return adapter(config).then(function onAdapterResolution(response) {

return response;

}, function onAdapterRejection(reason) {

return Promise.reject(reason);

});

}

).then(function(res){});

里面那层promise本身的resolve没问题,问题是里面那层promise的handler返回response如何能返回到外层promise的handler?

测试:

Promise.resolve().then(function(){

return Promise.resolve('hello').then(function (response) {

return response;

});

}

).then(function(res){

console.log(res);

});

结果response数据能传递给最后一层handler。

为了能debug,直接运行es6-promise.js文件覆盖浏览器缺省的es6-promise,在es6-promise.js文件末尾加一句执行

polyfill()即可。

从代码看,要new创建promise实例5次,debug看到的也是5个promise实例。

再看then代码,看第一次执行then的流程,第一次执行then执行里面的callback时是返回一个promise实例,而执行里层then的

情况不一样,此时执行里面的callback是返回response数据:

function then(onFulfillment, onRejection) {

var parent = this; // then是promise实例的内置方法,this就是then所在的promise实例

var child = new this.constructor(noop); //新建一个promise实例返回,是下一个then所在的promise实例

if (child[PROMISE_ID] === undefined) {

makePromise(child);

}

var _state = parent._state; //then所在的promise实例的状态,对于第一个then,它的promise实例是完成状态

if (_state) {

(function () {

var callback = _arguments[_state - 1];

asap(function () {

return invokeCallback(_state, child, callback, parent._result); //注意传递的是要返回的child实例

});

})();

} else {

subscribe(parent, child, onFulfillment, onRejection);

}

return child;

}

function invokeCallback(settled, promise, callback, detail) { //注意promise是then要返回的新建的child实例

if (hasCallback) {

value = tryCatch(callback, detail); //执行外层then里面的callback并获取callback的返回值(promise实例2),

但当执行里层then里面的callback时返回值是response。

else if (hasCallback && succeeded) { //如果then里面的callback执行成功

_resolve(promise, value); //将要返回的promise实例设置成完成状态并传递callback返回值(promise实例2)

function _resolve(promise, value) {

} else if (objectOrFunction(value)) {

handleMaybeThenable(promise, value, getThen(value));

} else {

fulfill(promise, value);

}

function handleMaybeThenable(promise, maybeThenable, then$$) {

if (maybeThenable.constructor === promise.constructor && then$$ === then && maybeThenable.constructor.resolve === resolve) {

handleOwnThenable(promise, maybeThenable);

function handleOwnThenable(promise, thenable) {

if (thenable._state === FULFILLED) {

fulfill(promise, thenable._result);

promise是外层then要返回的promise实例,在此解决它,传递值是里层promise实例2的result值,

也就是执行外层下一个then里面的handler并传递数据。

因此执行Promise.resolve().then(callback1)时,一是要返回一个promise实例,因为有可能连写.then(),二是要resolve返回的

promise实例才能执行后面可能连写的then(callback2),resolve情况如何取决于callback1的代码。

如果callback1的代码是return Promise.resolve().then(callback11),这就嵌套了,就非常复杂,首先,执行这个里层then

会执行callback11,取返回值response,然后resolve(promise,value)解决当前promise实例,会把value保存在当前promise

实例的_result中,因为后面没有再连写.then(),所以从这点来说返回当前promise实例其实没有用处,但对于外层promise是

有用的,里层then返回当前promise实例,按callback1的代码,这个callback执行结果就是返回这个promise实例,那么就回到

外层第一个then继续执行,外层then执行callback1获取到返回值之后,又会把then代码流程走一遍,但此时由于callback1

返回值是一个promise实例,处理流程有所不同,会取这个promise实例的_result值,再resolve(promise,value),其中promise

就是then本身返回的promise实例(then总是新建一个promise实例返回,再resolve这个实例,从而执行下一个then),这就会

执行下一个then里面的callback2并且传递value,因此最后一个then(function(res){}里面的callback能获取到'hello'数据。

如果写new Promise(callback1).then(callback2),意思是一样的,callback1代码决定第一个promise实例如何解决,

callback2代码决定如何解决then返回的promise实例,如果后面没有再连写then,就无需再写解决当前promise实例

(then返回的promise实例)的代码,反之就要写,连写then不复杂,嵌套比连写复杂。

promise代码的关键和难点在于如何resolve返回的promise实例,then需要resolve自己返回的promise实例,依此类推,

如果有嵌套,就更复杂了。

还有一点,就是执行顺序/异步问题,then是把callback存储起来,resolve时会找callback执行,一般是这个逻辑,很显然,

不能上来就执行then里面的callback。但执行then时会判断,如果then所在的promise实例已经完成,则会执行callback,

解决then本身返回的promise实例,以便执行到后面可能还有的then。所以then(callback)有可能在执行到resolve时执行,

也可能在执行then本身时就立即执行,取决于then所在的promise实例的状态,注意then本身返回的promise实例是下一个then

所在的promise实例,换句话说下一个连写的then就是then本身返回的promise实例的内置方法then,以http为例,.then写法

超越了jquery的$.ajax写法,逻辑上非常简单直观,但.then写法的代码原理其实非常复杂抽象深奥。

再回顾一下axios.get的写法:

Axios.prototype.request = function request(config) {

var chain = [dispatchRequest, undefined];

var promise = Promise.resolve(config);

while (chain.length) {

promise = promise.then(chain.shift(), chain.shift());

}

return promise;

其实就是Promise.resolve(config).then(拦截函数/request函数).then(function(res){

//http returned

},function(){

//http failed

});

创建promise实例传递config,它是用while循环把拦截函数都执行一遍,最后执行request,返回promise实例,

request代码又写了一层同样的嵌套,先完成http,再取response,再返回到外层继续执行下一个then()里面的

callback,也就是http最终的回调处理函数,代码设计非常高级精彩。

  • 结语

是不是有点晕?

promise从某种程度来说把事情搞复杂了,ajax写法多简单,人人分分钟就会写,前端框架其实从某种程度来说也是把事情搞得非常复杂,但它们有非常高的价值,还是应该使用它们,什么价值呢?就是应用代码可以写得更简洁更直观更高级更有档次,实现应用项目编程的模块化组件化层次化可复用,相比之下传统写法确实太low了,编程技术确实在进步,固守传统简单的编程技术是没有前途的,我们还是要勇于学习进步,这些源代码的作者他们是真正的编程高手大师。

上一篇:【C语言】C语言局部变量和全局变量


下一篇:29.使用register_chrdev_region()系列来注册字符设备