Promise源碼學習(1)

工做當中常常會用到Promise,在此進行深刻學習git

異步編程解決方案

Promise 是異步編程的一種解決方案,比傳統的解決方案——回調函數和事件——更合理和更強大。它由社區最先提出和實現,ES6 將其寫進了語言標準,統一了用法,原生提供了Promise對象.
關於其餘異步方案,有許多精彩的文章,在此再也不詳述。接下來直接進入正題。
項目地址This is a polyfill of the ES6 Promise
項目結構
圖片描述es6

new Promise()

let p = new Promise(function (resolve, reject) {
  console.log(1);
  resolve(2);
})
p.then(function (val) {
  console.log(val);
})

這是一段簡單實例,讓咱們跟着源碼一塊兒看發生了些什麼,代碼當中加入了我的理解的註釋。
首先看一下整理後的promise.jsgithub

class Promise {
  constructor (resolver) {
    this[PROMISE_ID] = nextId(); //生成id
    this._result = this._state = undefined;
    this._subscribers = [];//訂閱者

    //通常使用時,new時當即執行一次使用者傳入的resolver,印證了一旦promise開始執行沒法暫停
    if (noop !== resolver) {
      typeof resolver !== 'function' && needsResolver();
      this instanceof Promise ? initializePromise(this, resolver) : needsNew();//調用resolver
    }
  }

  catch (onRejection) {
    return this.then(null, onRejection);
  }

  // finally 至關於對當前promise註冊resolve和reject兩種監聽
  //若是爲 resolve 執行一次cb 而後把原來的value繼續傳遞
  finally (callback) {
    let promise = this;
    let constructor = promise.constructor;

    return promise.then(value => constructor.resolve(callback()).then(() => value),
      reason => constructor.resolve(callback()).then(() => {
        throw reason;
      }));
  }
}
Promise.prototype.then = then;
export default Promise;
Promise.all = all;
Promise.race = race;
Promise.resolve = Resolve;
Promise.reject = Reject;

細節均在註釋當中。
這裏主要是定義了Promise類和定義了一些方法如then,all等等,那麼new Promise()主要是初始化了對象的一些屬性,同時會當即執行resolver,印證了一旦promise開始執行沒法暫停。接下來繼續咱們的思路,看initializePromise方法發生了什麼。編程

//initializePromise(this, resolver)
function initializePromise (promise, resolver) {
  try {
    //執行resolver 傳入回調
    resolver(function resolvePromise (value) {
      resolve(promise, value);
    }, function rejectPromise (reason) {
      reject(promise, reason);
    });
  } catch (e) {
    reject(promise, e);
  }
}

這裏執行了咱們傳入的function,同時也給了使用者resolve和reject函數,因爲reject相對簡單,這裏咱們先看reject如何實現。promise

// 通用的reject方法
function reject (promise, reason) {
  if (promise._state !== PENDING) {
    return;
  }
  promise._state = REJECTED;
  promise._result = reason;

  asap(publishRejection, promise);//as soon as possible
}

reject方法中將promise的對象的狀態設置爲rejected,設置了執行的最終結果值result,隨後再asap(調度執行策略)中執行publishRejection,通知經過then方法註冊到_subscribers的訂閱者們,我被reject啦!!,來執行回調,下面是publishRejection方法。瀏覽器

function publishRejection (promise) {
  if (promise._onerror) {
    promise._onerror(promise._result);
  }

  publish(promise);
}

//通用的publish
function publish (promise) {
  let subscribers = promise._subscribers;
  let settled = promise._state;

  //沒有訂閱者
  if (subscribers.length === 0) {
    return;
  }

  let child, callback, detail = promise._result;

  //這裏i+=3 是由於then註冊時 i是promise,i+1是resolve,i+2是reject
  for (let i = 0; i < subscribers.length; i += 3) {
    child = subscribers[i];
    callback = subscribers[i + settled];

    if (child) {
      invokeCallback(settled, child, callback, detail);//執行回調
    } else {
      callback(detail);
    }
  }
  //通知完畢,清除訂閱
  promise._subscribers.length = 0;
}

上述就是reject以後的大體流程細節能夠看註釋。
讓咱們再回到這裏:異步

//initializePromise(this, resolver)
function initializePromise (promise, resolver) {
  try {
    //執行resolver 傳入回調
    resolver(function resolvePromise (value) {
      resolve(promise, value);
    }, function rejectPromise (reason) {
      reject(promise, reason);
    });
  } catch (e) {
    reject(promise, e);
  }
}

resolve發生了什麼呢?async

// 通用的resolve方法 繼續傳遞執行
function resolve (promise, value) {
  if (promise === value) {//若是resolve原對象
    reject(promise, selfFulfillment());//設置rejected狀態
  } else if (objectOrFunction(value)) {//若是val 是對象或函數
    handleMaybeThenable(promise, value, getThen(value));//getThen(value) 獲取val.then方法
  } else {//not obj or not fnc
    fulfill(promise, value);//設置pending result val
  }
}

resolve一個值,這裏分了三種狀況處理
一、若是resolve原對象,直接reject,拋錯。
二、若是是對象或函數,繼續處理。
三、若是是簡單值,//改變promise 狀態爲FULFILLED(完成狀態) 同時設置result,發起publish異步編程

若是非要簡單理解,resolve就是不斷抽絲剝繭的處理直到給promise一個肯定的完成態或拒絕態函數

fulfill方法比較簡單,asap下文介紹

//改變promise 狀態爲FULFILLED(完成狀態)  同時設置result
function fulfill (promise, value) {
  if (promise._state !== PENDING) {
    return;
  }

  promise._result = value;
  promise._state = FULFILLED;

  if (promise._subscribers.length !== 0) {//通知
    asap(publish, promise);
  }
}

這裏重點看handleMaybeThenable方法

/*maybeThenable:value;then:value.then*/
function handleMaybeThenable (promise, maybeThenable, then) {//thenable obj or promise
  /* originalThen : 定義的原始then;originalResolve:原始resolve*/
  if (maybeThenable.constructor === promise.constructor && //判斷是不是promise且沒經修改原生方法
    then === originalThen && maybeThenable.constructor.resolve === originalResolve) {
    handleOwnThenable(promise, maybeThenable);//maybeThenable是一個原生的promise
  } else {//maybeThenable
    if (then === TRY_CATCH_ERROR) {// getThen 拋錯
      reject(promise, TRY_CATCH_ERROR.error);
      TRY_CATCH_ERROR.error = null;//釋放引用
    } else if (then === undefined) {//若不是一個thenable,直接完成態
      fulfill(promise, maybeThenable);
    } else if (isFunction(then)) {//如果一個thenable
      handleForeignThenable(promise, maybeThenable, then);
    } else {//若不是一個thenable,直接完成態
      fulfill(promise, maybeThenable);//改變promise 狀態爲完成態  同時設置result
    }
  }
}

這裏就對傳入的值作了詳細區分
如果原生Promise對象:如果fulfilled或rejected,直接發起publish,若是是pending狀態,調用then來註冊訂閱回調。
如果thenable:特殊處理handleForeignThenable
'其餘fulfill或reject
這裏看一下handleForeignThenable方法

/*
 * thenable 是函數,有then方法
 * */
//handleForeignThenable(promise, maybeThenable, then);
function handleForeignThenable (promise, thenable, then) {
  asap(promise => {//asap這裏默認分析 setTimeout(fn,0) 下一輪任務開始時執行
    var sealed = false;//是否有結果
    //value:thenable傳入的參數 ,嘗試執行
    var error = tryThen(then, thenable, value => {//fullfill時,
      if (sealed) {
        return;
      }
      sealed = true;
      if (thenable !== value) {//若是不是直接resolve原對象
        resolve(promise, value);//繼續對resolve的val進行resolve處理
      } else {
        fulfill(promise, value);
      }
    }, reason => {//reject
      if (sealed) {
        return;
      }
      sealed = true;

      reject(promise, reason);
    }, 'Settle: ' + (promise._label || ' unknown promise'));

    if (!sealed && error) {//拋錯 未正常執行resolve reject
      sealed = true;
      reject(promise, error);
    }
  }, promise);
}

看起來比較亂,但思路比較簡單,這裏就對thenable進行嘗試執行,若是返回結果正常就繼續resolve處理直到解析出一個值,不然拋錯等等。

Then

上文說到了不少訂閱啦,publish啦,訂閱時哪裏來的呢,上文只看到了每次執行完狀態改變的時候要publish,publish給誰呢,then方法會給出答案

export default function then (onFulfillment, onRejection) {
  const parent = this;

  //新建一個不執行的promise對象用於返回結果,可鏈式調用
  const child = new this.constructor(noop);

  if (child[PROMISE_ID] === undefined) {//TODO
    makePromise(child);//初始化基本的promie 屬性
  }
  //promise state
  const {_state} = parent;

  if (_state) {// 若是狀態已完成或已拒絕,無需訂閱,直接執行回調返回結果,印證了一旦promise有告終果沒法再次改變
    const callback = arguments[_state - 1];
    asap(() => invokeCallback(_state, child, callback, parent._result));
  } else {//訂閱來註冊回調
    subscribe(parent, child, onFulfillment, onRejection);
  }
  //then reuturn的新promise
  return child;
}

這裏狀況分兩種
已完成或已拒絕:直接執行回調返回結果,印證了一旦promise有告終果沒法再次改變
pending未完成:訂閱來註冊回調
這裏先看invokeCallback方法。

//asap(() => invokeCallback(_state, child, callback, parent._result));
//執行回調:
function invokeCallback (settled, promise, callback, detail) {
  let hasCallback = isFunction(callback),
    value, error, succeeded, failed;

  if (hasCallback) {
    value = tryCatch(callback, detail);//嘗試執行使用者then()傳入的回調,成功時value 是then()註冊的回調方法的返回值

    if (value === TRY_CATCH_ERROR) {
      failed = true;
      error = value.error;
      value.error = null;
    } else {
      succeeded = true;
    }

    if (promise === value) {//若return this
      reject(promise, cannotReturnOwn());
      return;
    }

  } else {// then 未傳入相關回調,繼續傳遞
    value = detail;
    succeeded = true;
  }

  if (promise._state !== PENDING) {
    // noop
  } else if (hasCallback && succeeded) {
    resolve(promise, value);//value 可能爲thenable,繼續處理,抽絲剝繭
  } else if (failed) {//有cb 且失敗
    reject(promise, error);
  } else if (settled === FULFILLED) {//無cb
    fulfill(promise, value);
  } else if (settled === REJECTED) {//無cb
    reject(promise, value);
  }
}

接下來是重要的subscribe方法。

/*
 * parent:thenable
 * child : undefined or other
 * */
//subscribe(parent, child, onFulfillment, onRejection);
//若是promise還是pending,則將回調函數加入_subscribers等待通知
function subscribe (parent, child, onFulfillment, onRejection) {
  let {_subscribers} = parent;//取註冊的全部訂閱
  let {length} = _subscribers;

  parent._onerror = null;

  _subscribers[length] = child;//擴充訂閱  3個一循環
  _subscribers[length + FULFILLED] = onFulfillment;
  _subscribers[length + REJECTED] = onRejection;

  /*
  * 一、若是以前有訂閱且狀態是pending, 訂閱就行了,等待resolve完成時的發佈通知執行就好
  * 二、若是以前有訂閱且狀態不是pending,繼續加入訂閱就好,length=0時已經準備調度發佈了,pulish執行時會清空
  * 三、若是以前無訂閱且狀態是pending,訂閱就行了,等待resolve完成時的發佈通知執行就好
  * 四、以下,趕忙調度執行獲取結果
  * */
  if (length === 0 && parent._state) {//若是以前沒有訂閱且thenable已不是pending,
    asap(publish, parent);
  }
}

上面就是訂閱的過程,主要是利用的js單線程的特性,且須要和fuifill和reject執行時發佈publish一塊兒理解.
下面是asap方法

//下一輪事件循環執行
export var asap = function asap (callback, arg) {
  queue[len] = callback;//2個一組
  queue[len + 1] = arg;
  len += 2;
  if (len === 2) {
    /*
     若是隊列長度是2 ,那意味着咱們須要調度一次隊列flush,
     若是隊列flush完成前有其餘的回調進入隊列,這些進入的回調會在當前已調度的flush執行
     * */

    // If len is 2, 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.
    if (customSchedulerFn) {
      customSchedulerFn(flush);
    } else {//通常默認
      scheduleFlush();
    }
  }
}

function flush () {
  for (let i = 0; i < len; i += 2) {
    let callback = queue[i];
    let arg = queue[i + 1];

    callback(arg);

    queue[i] = undefined;
    queue[i + 1] = undefined;
  }

  len = 0;//邏輯清空隊列
}

本文默認分析採用的調度策略時setTimeout方法,asap裏維護了一個執行隊列queue。這裏涉及到了一些
js異步編程機制,推薦閱讀從瀏覽器多進程到JS單線程,JS運行機制最全面的一次梳理

結語

本文主要是跟着源碼的思路簡單過了一遍代碼,加入了我的的理解。同時還有一些如Promise.all等等方法將在下篇一塊兒分析。
閱讀代碼前,也學習了阮一峯老師關於Promise的文章,在此一併感謝。
全部源碼註釋見promise學習筆記

相關文章
相關標籤/搜索