深刻 Promise(一)——Promise 實現詳解

if (typeof Promise === 'undefined') {
  return
}

實現 Promise/A+ 規範的庫有不少,lie 是一個精簡的實現 Promise/A+ 的庫,而且經過了 Promise/A+ 專門的測試集,但 lie 的代碼寫的有點繞,我在 lie 的代碼基礎上進行了修改,使之更容易閱讀和理解,併發布了 appoint 模塊供你們參考。
Promise/A+ 規範node

Promise 規範有不少,如 Promise/A,Promise/B,Promise/D 以及 Promise/A 的升級版 Promise/A+,有興趣的能夠去了解下,最終 ES6 中採用了 Promise/A+ 規範。在講解 Promise 實現以前,固然要先了解 Promise/A+ 規範。Promise/A+ 規範參考:git

英文版:https://promisesaplus.com/
中文版:http://malcolmyu.github.io/ma...
注意:沒有特殊說明如下 promise 均指代 Promise 實例。
規範雖然不長,但細節也比較多,我挑出幾個要點簡單說明下:
Promise 本質是一個狀態機。每一個 promise 只能是 3 種狀態中的一種:pending、fulfilled 或 rejected。狀態轉變只能是 pending -> fulfilled 或者 pending -> rejected。狀態轉變不可逆。
then 方法能夠被同一個 promise 調用屢次。
then 方法必須返回一個 promise。規範裏沒有明確說明返回一個新的 promise 仍是複用老的 promise(即 return this),大多數實現都是返回一個新的 promise,並且複用老的 promise 可能改變內部狀態,這與規範也是相違背的。
值穿透。下面會細講。
從頭實現 Promisegithub

咱們知道 Promise 是一個構造函數,須要用 new 調用,並有如下幾個 api:api

function Promise(resolver) {}

Promise.prototype.then = function() {}
Promise.prototype.catch = function() {}

Promise.resolve = function() {}
Promise.reject = function() {}
Promise.all = function() {}
Promise.race = function() {}

下面咱們以 appoint 爲最終目標,開始一步一步構建完整的 Promise 實現。數組

'use strict';

var immediate = require('immediate');

function INTERNAL() {}
function isFunction(func) {
  return typeof func === 'function';
}
function isObject(obj) {
  return typeof obj === 'object';
}
function isArray(arr) {
  return Object.prototype.toString.call(arr) === '[object Array]';
}

var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;

module.exports = Promise;

function Promise(resolver) {
  if (!isFunction(resolver)) {
    throw new TypeError('resolver must be a function');
  }
  this.state = PENDING;
  this.value = void 0;
  this.queue = [];
  if (resolver !== INTERNAL) {
    safelyResolveThen(this, resolver);
  }
}

immediate 是一個將同步轉異步執行的庫。INTERNAL 就是一個空函數,相似於一些代碼庫中的 noop。定義了 3 個輔助函數:isFunction、isObject 和 isArray。定義了 3 種狀態:PENDING、FULFILLED 和 REJECTED。safelyResolveThen 後面講。promise 內部有三個變量:
state: 當前 promise 的狀態,初始值爲 PENDING。狀態改變只能是 PENDING -> FULFILLED 或 PENDING -> REJECTED。
value: 當 state 是 FULFILLED 時存儲返回值,當 state 是 REJECTED 時存儲錯誤。
queue: promise 內部的回調隊列,這是個什麼玩意兒?爲何是一個數組?
Promise 實現基本原理promise

先看一段代碼:安全

var Promise = require('appoint')
var promise = new Promise((resolve) => {
  setTimeout(() => {
    resolve('haha')
  }, 1000)
})
var a = promise.then(function onSuccess() {})
var b = promise.catch(function onError() {})
console.log(require('util').inspect(promise, { depth: 10 }))
console.log(promise.queue[0].promise === a)
console.log(promise.queue[1].promise === b)

打印出:併發

Promise {
  state: 0,
  value: undefined,
  queue:
   [ QueueItem {
       promise: Promise { state: 0, value: undefined, queue: [] },
       callFulfilled: [Function],
       callRejected: [Function] },
     QueueItem {
       promise: Promise { state: 0, value: undefined, queue: [] },
       callFulfilled: [Function],
       callRejected: [Function] } ] }
true
true

能夠看出,queue 數組中有兩個對象。由於規範中規定:then 方法能夠被同一個 promise 調用屢次。上例中在調用 .then 和 .catch 時 promise 並無被 resolve,因此將 .then 和 .catch 生成的新 promise(a 和 b) 和正確時的回調(onSuccess 包裝成 callFulfilled)和錯誤時的回調(onError 包裝成 callRejected)生成一個 QueueItem 實例並 push 到 queue 數組裏,因此上面兩個 console.log 打印 true。當 promise 狀態改變時遍歷內部 queue 數組,統一執行成功(FULFILLED -> callFulfilled)或失敗(REJECTED -> callRejected)的回調(傳入 promise 的 value 值),生成的結果分別設置 a 和 b 的 state 和 value,這就是 Promise 實現的基本原理。
再來看另外一個例子:app

var Promise = require('appoint')
var promise = new Promise((resolve) => {
  setTimeout(() => {
    resolve('haha')
  }, 1000)
})
promise
  .then(() => {})
  .then(() => {})
  .then(() => {})
console.log(require('util').inspect(promise, { depth: 10 }))

打印出:異步

Promise {
  state: 0,
  value: undefined,
  queue:
   [ QueueItem {
       promise:
        Promise {
          state: 0,
          value: undefined,
          queue:
           [ QueueItem {
               promise:
                Promise {
                  state: 0,
                  value: undefined,
                  queue:
                   [ QueueItem {
                       promise: Promise { state: 0, value: undefined, queue: [] },
                       callFulfilled: [Function],
                       callRejected: [Function] } ] },
               callFulfilled: [Function],
               callRejected: [Function] } ] },
       callFulfilled: [Function],
       callRejected: [Function] } ] }

調用了 3 次 then,每一個 then 將它生成的 promise 放到了調用它的 promise 隊列裏,造成了 3 層調用關係。當最外層的 promise 狀態改變時,遍歷它的 queue 數組調用對應的回調,設置子 promise 的 state 和 value 並遍歷它的 queue 數組調用對應的回調,而後設置孫 promise 的 state 和 value 並遍歷它的 queue 數組調用對應的回調......依次類推。
safelyResolveThen

function safelyResolveThen(self, then) {
  var called = false;
  try {
    then(function (value) {
      if (called) {
        return;
      }
      called = true;
      doResolve(self, value);
    }, function (error) {
      if (called) {
        return;
      }
      called = true;
      doReject(self, error);
    });
  } catch (error) {
    if (called) {
      return;
    }
    called = true;
    doReject(self, error);
  }
}

safelyResolveThen 顧名思義用來『安全的執行 then 函數』,這裏的 then 函數指『第一個參數是 resolve 函數第二個參數是 reject 函數的函數』,以下兩種狀況:
一、構造函數的參數,即這裏的 resolver:

new Promise(function resolver(resolve, reject) {
  setTimeout(() => {
    resolve('haha')
  }, 1000)
})

二、promise 的 then:

promise.then(resolve, reject)

safelyResolveThen 有 3 個做用:
一、try...catch 捕獲拋出的異常,如:

new Promise(function resolver(resolve, reject) {
throw new Error('Oops')
})

二、called 控制 resolve 或 reject 只執行一次,屢次調用沒有任何做用。即:

var Promise = require('appoint')
var promise = new Promise(function resolver(resolve, reject) {
  setTimeout(() => {
    resolve('haha')
  }, 1000)
  reject('error')
})
promise.then(console.log)
promise.catch(console.error)

打印 error,不會再打印 haha。
三、沒有錯誤則執行 doResolve,有錯誤則執行 doReject。

doResolve 和 doReject

function doResolve(self, value) {
  try {
    var then = getThen(value);
    if (then) {
      safelyResolveThen(self, then);
    } else {
      self.state = FULFILLED;
      self.value = value;
      self.queue.forEach(function (queueItem) {
        queueItem.callFulfilled(value);
      });
    }
    return self;
  } catch (error) {
    return doReject(self, error);
  }
}

function doReject(self, error) {
  self.state = REJECTED;
  self.value = error;
  self.queue.forEach(function (queueItem) {
    queueItem.callRejected(error);
  });
  return self;
}

doReject 就是設置 promise 的 state 爲 REJECTED,value 爲 error,callRejected 如前面提到的通知子 promise:『我這裏出了點問題呀』而後子 promise 根據傳入的錯誤設置本身的狀態和值。doResolve 結合 safelyResolveThen 使用不斷地解包 promise,直至返回值是非 promise 對象後,設置 promise 的狀態和值,而後通知子 promise:『我這裏有值了喲』而後子 promise 根據傳入的值設置本身的狀態和值。

這裏有個輔助函數 getThen:

function getThen(obj) {
  var then = obj && obj.then;
  if (obj && (isObject(obj) || isFunction(obj)) && isFunction(then)) {
    return function appyThen() {
      then.apply(obj, arguments);
    };
  }
}

規範中規定:若是 then 是函數,將 x(這裏是 obj) 做爲函數的 this 調用。
Promise.prototype.then 和 Promise.prototype.catch

Promise.prototype.then = function (onFulfilled, onRejected) {
  if (!isFunction(onFulfilled) && this.state === FULFILLED ||
    !isFunction(onRejected) && this.state === REJECTED) {
    return this;
  }
  var promise = new this.constructor(INTERNAL);
  if (this.state !== PENDING) {
    var resolver = this.state === FULFILLED ? onFulfilled : onRejected;
    unwrap(promise, resolver, this.value);
  } else {
    this.queue.push(new QueueItem(promise, onFulfilled, onRejected));
  }
  return promise;
};

Promise.prototype.catch = function (onRejected) {
  return this.then(null, onRejected);
};

上述代碼中的 return this 實現了值穿透,後面會講。能夠看出,then 方法中生成了一個新的 promise 而後返回,符合規範要求。若是 promise 的狀態改變了,則調用 unwrap,不然將生成的 promise 加入到當前 promise 的回調隊列 queue 裏,以前講解了如何消費 queue。有 3 點須要講解:
一、Promise 構造函數傳入了一個 INTERNAL 即空函數,由於這個新產生的 promise 能夠認爲是內部的 promise,須要根據外部的 promise 的狀態和值產生自身的狀態和值,不須要傳入回調函數,而外部 Promise 須要傳入回調函數決定它的狀態和值。因此以前 Promise 的構造函數裏作了判斷區分外部調用仍是內部調用:

if (resolver !== INTERNAL) {
  safelyResolveThen(this, resolver);
}

二、unwrap 代碼以下:

function unwrap(promise, func, value) {
  immediate(function () {
    var returnValue;
    try {
      returnValue = func(value);
    } catch (error) {
      return doReject(promise, error);
    }
    if (returnValue === promise) {
      doReject(promise, new TypeError('Cannot resolve promise with itself'));
    } else {
      doResolve(promise, returnValue);
    }
  });
}

從名字也能夠理解是用來解包(即執行函數)的,第一個參數是子 promise,第二個參數是父 promise 的 then 的回調(onFulfilled/onRejected),第三個參數是父 promise 的值(正常值/錯誤)。有 3 點須要說明:
一、使用 immediate 將同步代碼變異步。如:

var Promise = require('appoint')
var promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('haha')
  }, 1000)
})
promise.then(() => {
  promise.then(() => {
    console.log('1')
  })
  console.log('2')
})

打印 2 1,去掉 immediate 則打印 1 2。
二、try...catch 用來捕獲 then/catch 內拋出的異常,並調用 doReject,如:

promise.then(() => {
  throw new Error('haha')
})
promise.catch(() => {
  throw new Error('haha')
})

三、返回的值不能是 promise 自己,不然會形成死循環,如 node@4.6.0 下運行:

var promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('haha')
  }, 1000)
})
var a = promise.then(() => {
  return a
})

a.catch(console.log)// [TypeError: Chaining cycle detected for promise #<Promise>]

QueueItem 代碼以下:

function QueueItem(promise, onFulfilled, onRejected) {
  this.promise = promise;
  this.callFulfilled = function (value) {
    doResolve(this.promise, value);
  };
  this.callRejected = function (error) {
    doReject(this.promise, error);
  };
  if (isFunction(onFulfilled)) {
    this.callFulfilled = function (value) {
      unwrap(this.promise, onFulfilled, value);
    };
  }
  if (isFunction(onRejected)) {
    this.callRejected = function (error) {
      unwrap(this.promise, onRejected, error);
    };
  }
}
promi

se 爲 then 生成的新 promise(如下稱爲『子promise』),onFulfilled 和 onRejected 便是 then 參數中的 onFulfilled 和 onRejected。從上面代碼能夠看出:好比當 promise 狀態變爲 FULFILLED 時,以前註冊的 then 函數,用 callFulfilled 調用 unwrap 進行解包最終得出子 promise 的狀態和值,以前註冊的 catch 函數,用 callFulfilled 直接調用 doResolve,設置隊列裏子 promise 的狀態和值。當 promise 狀態變爲 REJECTED 相似。
注意:promise.catch(onRejected) 就是 promise.then(null, onRejected) 的語法糖。
至此,Promise 的核心實現都完成了。
值穿透

Promise.prototype.then = function (onFulfilled, onRejected) {
  if (!isFunction(onFulfilled) && this.state === FULFILLED ||
    !isFunction(onRejected) && this.state === REJECTED) {
    return this;
  }
  ...
};

上面提到了值穿透問題,值穿透即:

var Promise = require('appoint')
var promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('haha')
  }, 1000)
})
promise
  .then('hehe')
  .then(console.log)

最終打印 haha 而不是 hehe。
經過 return this 只實現了值穿透的一種狀況,其實值穿透有兩種狀況:
一、promise 已是 FULFILLED/REJECTED 時,經過 return this 實現的值穿透:

var Promise = require('appoint')
var promise = new Promise(function (resolve) {
  setTimeout(() => {
    resolve('haha')
  }, 1000)
})
promise.then(() => {
  promise.then().then((res) => {// ①
    console.log(res)// haha
  })
  promise.catch().then((res) => {// ②
    console.log(res)// haha
  })
  console.log(promise.then() === promise.catch())// true
  console.log(promise.then(1) === promise.catch({ name: 'nswbmw' }))// true
})

上述代碼①②處 promise 已是 FULFILLED 了符合條件因此執行了 return this。注意:原生的 Promise 實現裏並非這樣實現的,因此會打印兩個 false。
二、promise 是 PENDING 時,經過生成新的 promise 加入到父 promise 的 queue,父 promise 有值時調用 callFulfilled->doResolve 或 callRejected->doReject(由於 then/catch 傳入的參數不是函數)設置子 promise 的狀態和值爲父 promise 的狀態和值。如:

var Promise = require('appoint')
var promise = new Promise((resolve) => {
  setTimeout(() => {
    resolve('haha')
  }, 1000)
})
var a = promise.then()
a.then((res) => {
  console.log(res)// haha
})
var b = promise.catch()
b.then((res) => {
  console.log(res)// haha
})
console.log(a === b)// false

Promise.resolve 和 Promise.reject

Promise.resolve = resolve;
function resolve(value) {
  if (value instanceof this) {
    return value;
  }
  return doResolve(new this(INTERNAL), value);
}

Promise.reject = reject;
function reject(reason) {
  var promise = new this(INTERNAL);
  return doReject(promise, reason);
}

當 Promise.resolve 參數是一個 promise 時,直接返回該值。

Promise.all

Promise.all = all;
function all(iterable) {
  var self = this;
  if (!isArray(iterable)) {
    return this.reject(new TypeError('must be an array'));
  }

  var len = iterable.length;
  var called = false;
  if (!len) {
    return this.resolve([]);
  }

  var values = new Array(len);
  var resolved = 0;
  var i = -1;
  var promise = new this(INTERNAL);

  while (++i < len) {
    allResolver(iterable[i], i);
  }
  return promise;
  function allResolver(value, i) {
    self.resolve(value).then(resolveFromAll, function (error) {
      if (!called) {
        called = true;
        doReject(promise, error);
      }
    });
    function resolveFromAll(outValue) {
      values[i] = outValue;
      if (++resolved === len && !called) {
        called = true;
        doResolve(promise, values);
      }
    }
  }
}

Promise.all 用來並行執行多個 promise/值,當全部 promise/值執行完畢後或有一個發生錯誤時返回。能夠看出:
一、Promise.all 內部生成了一個新的 promise 返回。
二、called 用來控制即便有多個 promise reject 也只有第一個生效。
三、values 用來存儲結果。
四、當最後一個 promise 得出結果後,使用 doResolve(promise, values) 設置 promise 的 state 爲 FULFILLED,value 爲結果數組 values。
Promise.race

Promise.race = race;
function race(iterable) {
  var self = this;
  if (!isArray(iterable)) {
    return this.reject(new TypeError('must be an array'));
  }

  var len = iterable.length;
  var called = false;
  if (!len) {
    return this.resolve([]);
  }

  var i = -1;
  var promise = new this(INTERNAL);

  while (++i < len) {
    resolver(iterable[i]);
  }
  return promise;
  function resolver(value) {
    self.resolve(value).then(function (response) {
      if (!called) {
        called = true;
        doResolve(promise, response);
      }
    }, function (error) {
      if (!called) {
        called = true;
        doReject(promise, error);
      }
    });
  }
}

Promise.race 接受一個數組,當數組中有一個 resolve 或 reject 時返回。跟 Promise.all 代碼相近,只不過這裏用 called 控制只要有任何一個 promise onFulfilled/onRejected 當即去設置 promise 的狀態和值。至此,Promise 的實現所有講解完畢。

相關文章
相關標籤/搜索