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了,編程技術確實在進步,固守傳統簡單的編程技術是沒有前途的,我們還是要勇於學習進步,這些源代碼的作者他們是真正的編程高手大師。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM