zoukankan      html  css  js  c++  java
  • 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了,编程技术确实在进步,固守传统简单的编程技术是没有前途的,我们还是要勇于学习进步,这些源代码的作者他们是真正的编程高手大师。

  • 相关阅读:
    CF1270H. Number of Components
    NOI Online Round2划水记
    uoj#247. 【Rujia Liu's Present 7】Mysterious Space Station口胡
    mysql习题
    MySQL基础
    python网络编程(进程与多线程)
    xshell连接虚拟机Ubuntu问题
    python来写打飞机
    timeit模块
    python常用模块
  • 原文地址:https://www.cnblogs.com/pzhu1/p/8365963.html
Copyright © 2011-2022 走看看