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

Posted pzhu1

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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了,编程技术确实在进步,固守传统简单的编程技术是没有前途的,我们还是要勇于学习进步,这些源代码的作者他们是真正的编程高手大师。

 

以上是关于es6-promise源代码重点难点分析的主要内容,如果未能解决你的问题,请参考以下文章

vue 2.0 路由切换以及组件缓存源代码重点难点分析

vue 1.0源代码重点难点分析

Java-Java温习之Java中的重点和难点

零基础学UI设计教程分享C4D中重点难点分析

DataWorks 重点难点

HTML和CSS必须知道的重点难点问题