Promise源碼解析

Promise簡介

Promise 是異步編程的一種解決方案,比傳統的異步解決方案【回調函數】和【事件】更合理、更強大。現已被 ES6 歸入進規範中。git

使用方式

new Promise((resolve, reject) => {
    ajax({
        success(res) {
            resolve(res);
        },
        error(err) {
            reject(err);
        }
    })
}).then((res) => {
    // 請求正常處理
})
    .catch((err) => {
        // 錯誤處理
    })
複製代碼

基本源碼分析

成功回調處理

首先咱們須要一個promise的使用實例代碼,以下:github

示例代碼1

new Promise((resolve) => {
    setTimeout(() => {
        resolve(123);
    }, 0);
}).then((res) => {
    console.log(res);
})
複製代碼

事例代碼中,首先new了一個Promise對象,源碼中Promise對象的構造函數長這樣:ajax

Promise構造函數

function Promise(fn) {
    if (typeof this !== 'object') {
        throw new TypeError('Promises must be constructed via new');
    }
    if (typeof fn !== 'function') {
        throw new TypeError('Promise constructor’s argument is not a function');
    }
    this._deferredState = 0; // then的狀態
    this._state = 0; // 狀態,初始狀態是0表示pending,1表示fulfilled,2表示rejected
    this._value = null; // 獲取到的值
    this._deferreds = null; //  存儲的成功回調和失敗回調,以及返回的新Promis的handler
    if (fn === noop) return;
    doResolve(fn, this);
}
複製代碼

先檢測是否用new的方式實例化promise對象,再檢查傳入的方法是否爲方法,若檢查都經過,就初始化一些參數,以後將傳入promise的方法fn和promise實例做爲參數傳給doResolve方法。doResolve方法以下:編程

doResolve方法

function doResolve(fn, promise) {
    var done = false;
    var res = tryCallTwo(fn, function (value) {
        if (done) return;
        done = true;
        resolve(promise, value);
    }, function (reason) {
        if (done) return;
        done = true;
        reject(promise, reason);
    });
    if (!done && res === IS_ERROR) {
        done = true;
        reject(promise, LAST_ERROR);
    }
}
複製代碼

tryCallTwo方法接受3個參數,第一個是傳給Promise的異步方法fn,真實調用的resolve方法,以及真實調用的reject方法。tryCallTwo方法以下:數組

tryCallTwo

function tryCallTwo(fn, a, b) {
  try {
    fn(a, b); // 執行傳入promise的方法,而且將resolve和reject方法做爲參數傳入
  } catch (ex) {
    LAST_ERROR = ex;
    return IS_ERROR;
  }
}
複製代碼

異步操做還未返回

由於咱們的示例只是一個setTimeout的一個回調,因此不會走到報錯的環節,setTimeout會放入任務隊列中,延遲執行resolve。接下來會先執行then方法,Promise中的then方法掛在prototype上面:promise

then方法

Promise.prototype.then = function(onFulfilled, onRejected) {
  if (this.constructor !== Promise) {
    return safeThen(this, onFulfilled, onRejected);
  }
  var res = new Promise(noop);
  handle(this, new Handler(onFulfilled, onRejected, res));
  return res;
};
複製代碼
  • 若是不是在Promise實例上調用then會調用safeThen(這一段我也不是很理解,會有不在Promise實例調用then的狀況嘛?但願有大神能解釋)。
  • 如果正常Promise實例調用then,則新建一個Promise實例(then中返回的實例,用於鏈式調用)
  • 把正確的回調和報錯的回調以及返回的Promise實例都做爲參數,傳入Handler的構造方法中。Handler構造方法以下:

Handler構造函數

function Handler(onFulfilled, onRejected, promise){
  this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
  this.onRejected = typeof onRejected === 'function' ? onRejected : null;
  this.promise = promise;
}
複製代碼

Handler的構造方法僅僅只是將成功回調和失敗回調,以及返回的下一個Promise實例做爲私有變量保存下來。接着將Handler實例做爲參數傳入handle方法中:bash

handle(this, new Handler(onFulfilled, onRejected, res));
複製代碼

handle方法代碼以下:異步

handle方法

function handle(self, deferred) {
  while (self._state === 3) { // 處理resolve的值是個promise實例的狀況
    self = self._value;
  }
  if (Promise._onHandle) { // 若定義了_onHandle方法,則調用
    Promise._onHandle(self);
  }
  if (self._state === 0) { // 異步請求還沒返回
    if (self._deferredState === 0) { // then方法以前還未調用
      self._deferredState = 1; // 進入改邏輯說明then已經調用,改成狀態爲2
      self._deferreds = deferred; // handler
      return;
    }
    if (self._deferredState === 1) { // Promise實例又執行了一遍then
      self._deferredState = 2; // 在請求未返回狀況下,第二次調用then,狀態爲2
      self._deferreds = [self._deferreds, deferred]; // 在請求未返回狀況下,第二次調用then,deferreds改爲數組,存儲多個handler
      return;
    }
    self._deferreds.push(deferred); // 在請求未返回狀況下,屢次調用then,將handler依次放入隊列
    return;
  }
  handleResolved(self, deferred);
}
複製代碼

此時咱們的邏輯走到這裏,把then狀態修改成1,存儲了handler後返回:異步編程

if (self._deferredState === 0) {
      self._deferredState = 1;
      self._deferreds = deferred;
      return;
}
複製代碼

異步請求返回啦

回顧doResolve方法:函數

function doResolve(fn, promise) {
    var done = false;
    var res = tryCallTwo(fn, function (value) {
        if (done) return;
        done = true;
        resolve(promise, value); // 實例代碼中resolve(123),因此邏輯走到這裏
    }, function (reason) {
        if (done) return;
        done = true;
        reject(promise, reason);
    });
    if (!done && res === IS_ERROR) {
        done = true;
        reject(promise, LAST_ERROR);
    }
}
複製代碼

resolve方法以下:

resolve方法

function resolve(self, newValue) {
  // Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure
  if (newValue === self) {
    return reject(
      self,
      new TypeError('A promise cannot be resolved with itself.')
    );
  }
  if (
    newValue &&
    (typeof newValue === 'object' || typeof newValue === 'function')
  ) {
    var then = getThen(newValue);
    if (then === IS_ERROR) {
      return reject(self, LAST_ERROR);
    }
    if (
      then === self.then &&
      newValue instanceof Promise
    ) {
      self._state = 3;
      self._value = newValue;
      finale(self);
      return;
    } else if (typeof then === 'function') {
      doResolve(then.bind(newValue), self);
      return;
    }
  }
  self._state = 1;
  self._value = newValue;
  finale(self);
}
複製代碼

此時咱們只是resolve了一個123,既不是對象也不是Promise,因而只走這段邏輯:

self._state = 1; // state爲1表示fulfilled
self._value = newValue; // 123
finale(self);
複製代碼

執行finale方法:

finale

function finale(self) {
  if (self._deferredState === 1) { // then已經執行
    handle(self, self._deferreds); // 走到這裏
    self._deferreds = null;
  }
  if (self._deferredState === 2) { // 多從調用then,則通知全部then註冊的回調所有執行
    for (var i = 0; i < self._deferreds.length; i++) {
      handle(self, self._deferreds[i]);
    }
    self._deferreds = null;
  }
}
複製代碼

再次執行handle方法,此時state和defferedSate值都爲1,則執行handleResolved方法

handleResolved(self, deferred);
複製代碼

handleResolved

function handleResolved(self, deferred) {
  asap(function() {
    var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
    if (cb === null) {
      if (self._state === 1) {
        resolve(deferred.promise, self._value);
      } else {
        reject(deferred.promise, self._value);
      }
      return;
    }
    var ret = tryCallOne(cb, self._value);
    if (ret === IS_ERROR) {
      reject(deferred.promise, LAST_ERROR);
    } else {
      resolve(deferred.promise, ret);
    }
  });
}
複製代碼
  • asap是一個第三方庫,功能相似與setImmediate
  • 判斷當前state的狀態,決定要執行的回調是成功回調仍是失敗回調
  • 若沒設置過回調,則把value傳給下一個Promise
  • 執行tryCallOne

tryCallOne

function tryCallOne(fn, a) {
  try {
    return fn(a);
  } catch (ex) {
    LAST_ERROR = ex;
    return IS_ERROR;
  }
}
複製代碼

在tryCallOne中執行對應回調,若沒有報錯的話,將返回值做爲value傳遞給下一個Promise。咱們的示例代碼中的console.log(res)就會執行,打印出123。

一次不報錯的Promise調用流程

咱們整理一下,若是調用一次Promise,且沒發生報錯的狀況下,發生了什麼事情

錯誤回調處理

若Promise中傳入的方法,發生錯誤,這時候會發生什麼?

示例代碼2

new Promise((resolve) => {
    resolve(a); // a並無定義,發生報錯
}).then((res) => {
    console.log(res);
})
複製代碼

回到源碼中到doResolve方法以及內部的tryCallTwo方法

// doResolve
function doResolve(fn, promise) {
  var done = false;
  var res = tryCallTwo(fn, function (value) {
    if (done) return;
    done = true;
    resolve(promise, value);
  }, function (reason) {
    if (done) return;
    done = true;
    reject(promise, reason);
  });
  if (!done && res === IS_ERROR) {
    done = true;
    reject(promise, LAST_ERROR);
  }
}
// tryCallTwo
function tryCallTwo(fn, a, b) {
  try {
    fn(a, b);
  } catch (ex) {
    LAST_ERROR = ex;
    return IS_ERROR;
  }
}
複製代碼

tryCallTwo方法裏使用了try-catch,若發生報錯LAST_ERROR會保存此次報錯的信息,並返回一個錯誤。以後promise會自動執行reject。

reject

function reject(self, newValue) {
  self._state = 2;
  self._value = newValue;
  if (Promise._onReject) {
    Promise._onReject(self, newValue);
  }
  finale(self);
}
複製代碼

reject跟resolve方法相似,只是把state狀態變成了2而已,value保存爲此次報錯的信息。而後then方法中調用handleResolved時候,選擇執行第二個傳入的方法。

var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
複製代碼

但咱們大多數開發的時候,then都沒有傳入第二個方法,而是使用catch來捕獲錯誤。

new Promise((resolve) => {
    resolve(a); // a並無定義,發生報錯
}).then((res) => {
    console.log(res);
})
    .catch((err) => {
        // ...
    })
複製代碼

catch的實現很是簡單,只是調用了一下then方法,可是不傳入正確回調的方法。

catch

Promise.prototype['catch'] = function (onRejected) {
  return this.then(null, onRejected);
};
複製代碼

不過有一種狀況,在異步操做返回以後的報錯,try-catch沒法捕獲到,這時候須要在返回的方法體中手動reject報錯信息。

new Promise((resolve, reject) => {
    setTimeout(() => {
        try {
            resolve(a); // a並無定義,發生報錯
        } catch(err) {
            reject(err); // 由於異步的報錯,須要手動reject
        }
    }, 0);
}).then((res) => {
    console.log(res);
})
    .catch((err) => {
        // ...
    })
複製代碼

Promise正常使用的流程圖

resolve一個Promise實例

實例代碼3

new Promise((resolve) => {
    resolve(new Promise((resolve) => {
        resolve(111);
    }));
}).then((res) => {
    console.log(111);
});
複製代碼

resolve方法裏面,若是resolve的是一個Promise會走入如下邏輯:

if (
      then === self.then &&
      newValue instanceof Promise
    ) {
      self._state = 3;
      self._value = newValue;
      finale(self);
      return;
    }
複製代碼

狀態值設置爲3,而且把resolve內部的Promise實例賦值給value。 handle方法中的處理

while (self._state === 3) { // state狀態爲3的時候,把resolve內的promise設置爲當前promise
    self = self._value;
  }
}
複製代碼

因而Promise內resolve一個Promise的時候,接下來調用的then方法中回調中的參數是resolve內部的Promise的value。代碼3中打印出來的值應該是111。

then中return一個Promise

鏈式調用then的時候,then中的回調執行的值,將做爲返回的Promise的value,也就是下一次then時候傳給回調的值。

new Promise((resolve) => {
    resolve(1);
}).then((resolve) => {
    resolve(2);
}).then((res) => {
    console.log(res); // 2
})
複製代碼

在Promise代碼執行完畢後,handleResolved方法後面還有一個這樣的邏輯

var ret = tryCallOne(cb, self._value);
if (ret === IS_ERROR) {
  reject(deferred.promise, LAST_ERROR);
} else {
  resolve(deferred.promise, ret);
}
複製代碼

若是回調方法沒有報錯,回調函數return的值將用resolve的方式做爲value傳給then中返回的Promise。那若是return一個Promise將會和resolve(new Promise)同樣,return的Promise將取代原先then中返回的Promise。下一個then中傳遞的參數也將是手動return的Promise的value。如:

new Promise((resolve) => {
    resolve(1);
}).then((res) => {
    return new Promise((resolve) => {
        resolve(2);
    });
}).then((res) => {
    console.log(res) // 2
})
複製代碼

Promise.all

有時候須要多個請求並行發送,就要使用到Promise.all

Promise.all源碼

Promise.all = function (arr) {
  var args = Array.prototype.slice.call(arr);
  return new Promise(function (resolve, reject) {
    if (args.length === 0) return resolve([]);
    var remaining = args.length;
    function res(i, val) {
      if (val && (typeof val === 'object' || typeof val === 'function')) {
        if (val instanceof Promise && val.then === Promise.prototype.then) {
          while (val._state === 3) {
            val = val._value;
          }
          if (val._state === 1) return res(i, val._value);
          if (val._state === 2) reject(val._value);
          val.then(function (val) {
            res(i, val);
          }, reject);
          return;
        } else {
          var then = val.then;
          if (typeof then === 'function') {
            var p = new Promise(then.bind(val));
            p.then(function (val) {
              res(i, val);
            }, reject);
            return;
          }
        }
      }
      args[i] = val;
      if (--remaining === 0) {
        resolve(args);
      }
    }
    for (var i = 0; i < args.length; i++) {
      res(i, args[i]);
    }
  });
};
複製代碼

能夠看出Promise.all也是返回了一個Promise,這個Promise內部resolve了一個返回全部結果的數組。Promise.all會接受一個數組,以後遍歷這個數組,數組的每一個成員都會執行一遍res這個方法

res

function res(i, val) {
      if (val && (typeof val === 'object' || typeof val === 'function')) {
        if (val instanceof Promise && val.then === Promise.prototype.then) {
          while (val._state === 3) {
            val = val._value;
          }
          if (val._state === 1) return res(i, val._value);
          if (val._state === 2) reject(val._value);
          val.then(function (val) {
            res(i, val);
          }, reject);
          return;
        } else {
          var then = val.then;
          if (typeof then === 'function') {
            var p = new Promise(then.bind(val));
            p.then(function (val) {
              res(i, val);
            }, reject);
            return;
          }
        }
      }
      args[i] = val;
      if (--remaining === 0) {
        resolve(args);
      }
    }
複製代碼

判斷傳入Promise實例的狀態:

若是state爲3(裏面resolve了Promise)當前的Promise更換爲value。

state爲2說明,隊列中的該Promise內部狀態爲reject,則直接在Promise.all執行reject。因此爲何Promise.all中,隊列中有一個Promise實行失敗,整個Promise.all都會進入失敗回調。

state爲1,則直接調用res(i, value)。

state狀態爲0,改promise成員直接調用then方法,獲取其中value,再調用res(i, value)。

當隊員成員Promise的value已經返回後,再次調用res(i, value),此時的value已經不是個Promise,將值賦到數組對應位子。

args[i] = val;
複製代碼

所有Promise成員執行完畢後,resolve數組

if (--remaining === 0) {
    resolve(args);
  }
複製代碼

Promise.resolve

Promise.resolve源碼

var TRUE = valuePromise(true);
var FALSE = valuePromise(false);
var NULL = valuePromise(null);
var UNDEFINED = valuePromise(undefined);
var ZERO = valuePromise(0);
var EMPTYSTRING = valuePromise('');

function valuePromise(value) {
  var p = new Promise(Promise._noop);
  p._state = 1;
  p._value = value;
  return p;
}
Promise.resolve = function (value) {
  if (value instanceof Promise) return value;

  if (value === null) return NULL;
  if (value === undefined) return UNDEFINED;
  if (value === true) return TRUE;
  if (value === false) return FALSE;
  if (value === 0) return ZERO;
  if (value === '') return EMPTYSTRING;

  if (typeof value === 'object' || typeof value === 'function') {
    try {
      var then = value.then;
      if (typeof then === 'function') {
        return new Promise(then.bind(value));
      }
    } catch (ex) {
      return new Promise(function (resolve, reject) {
        reject(ex);
      });
    }
  }
  return valuePromise(value);
};
複製代碼

這裏用到了一個valuePromise的方法,這個方法接受一個值,而且設置一個新的Promise,將這個Promise的狀態直接設置爲1,value設置爲傳入值,直接返回這個Promise。

function valuePromise(value) {
  var p = new Promise(Promise._noop);
  p._state = 1;
  p._value = value;
  return p;
}
複製代碼

Promise.all要分爲3中狀況分析:

1.傳入一個值(如果對象或方法,則其中沒有then的私有方法),那麼直接將這個值傳給valuePromise,並將其返回

if (value === null) return NULL;
if (value === undefined) return UNDEFINED;
if (value === true) return TRUE;
if (value === false) return FALSE;
if (value === 0) return ZERO;
if (value === '') return EMPTYSTRING;
return valuePromise(value);
複製代碼

用法上:

Promise.resolve(1).then((res) => {
    console.log(res) // 打印出1,Promise.resolve直接返回了一個新的Promise,它的值是1
})
複製代碼

2.傳入一個Promise,則直接將這個Promise返回。

f (value instanceof Promise) return value;
複製代碼

3.傳入一個含有then方法的對象或方法,會調用它的then方法。

try {
  var then = value.then;
  if (typeof then === 'function') {
    return new Promise(then.bind(value));
  }
} catch (ex) {
  return new Promise(function (resolve, reject) {
    reject(ex);
  });
}
複製代碼

Promise.finally

then方法只在promise內方法執行成功時執行;catch方法只在Promise內方法執行失敗了執行;有些時候無論方法體執行成功或失敗,都要執行某些特定方法,這個時候就要使用finally。

示例代碼4

new Promise((resolve) => {
    resolve(1);
}).then((res) => {
    ...
})
    catch((err) => {
        ...
    })
        .finally(() => {
            ...
        })
複製代碼

源碼中finally的代碼以下:

Promise.prototype.finally = function (f) {
  return this.then(function (value) {
    return Promise.resolve(f()).then(function () {
      return value;
    });
  }, function (err) {
    return Promise.resolve(f()).then(function () {
      throw err;
    });
  });
};
複製代碼

finally中只是返回了this.then,而後在then中的成功失敗回調中都執行了傳入的f方法。不過既然返回了this.then,也就意味着,finally後面能夠繼續鏈式調用then。

總結

Promise是一個構造函數,裏面有2個核心的控制狀態的變量:

  1. state主體方法執行的狀態值,0表明初始值,1表明請求成功,2表明失敗,3表明resolve了一個Promise實例
  2. defferedState是then執行的狀態值,0表明then方法未執行,1表明執行了一個then方法,2表示then方法執行了屢次。

主要執行邏輯:

  1. 主體函數方法執行,通常是一個異步方法,執行完畢後若成功將state設置爲1,失敗設置爲2;
  2. 檢查defferedState值是否爲0,如果0則等待then執行,不是則執行then中記錄的回調。
  3. then方法執行,先設置一個Promise實例,並將其返回;將成功回調和失敗回調記錄下來,將defferedState設置爲1。
  4. 檢查state是否爲0,爲0則等待異步請求返回,1則執行成功方法,2執行失敗方法。
  5. 將執行的回調返回值,用resolve的方式賦值給then中返回的Promise。
相關文章
相關標籤/搜索