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執行終結。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章