手寫 Promise 符合 Promises/A+規範

異步編程是前端開發者必需的技能,過去管理異步的主要機制都是經過函數回調,然而會出現像「回調地獄」這樣的問題。爲了更好的管理回調,ES6 增長了一個新的特性 PromisePromiseES7async/await 語法的基礎,是 JavaScript 中處理異步的標準形式,現實開發中基本離不開 Promise 了。咱們接下來會根據 Promises/A+ 規範本身實現一個 Promise前端

完整的代碼能夠點擊個人 github 進行查看,若是你喜歡,歡迎 star,若是發現有問題或者錯誤,也歡迎提出來。git

Promises/A+ 規範

首先咱們來看 Promises/A+ 規範的具體內容,Promises/A+ 規範能夠查看 Promises/A+,下面是翻譯的規範供參考github

術語
  1. promise 是一個有 then 方法的對象或者是函數,行爲遵循本規範
  2. thenable 是一個有 then 方法的對象或者是函數
  3. valuepromise 狀態成功時的值,包括 undefinedthenablepromise
  4. exception 是一個使用 throw 拋出的異常值
  5. reasonpromise 狀態失敗時的值
要求

2.1 Promise 狀態

Promise 必須處於如下三個狀態之一: pending, fulfilled 或者 rejectednpm

2.1.1 若是 promise 在 pending 狀態
    2.1.1.1 能夠變成 fulfilled 或者是 rejected
2.1.2 若是 promise 在 fulfilled 狀態
    2.1.2.1 不會變成其它狀態
    2.1.2.2 必須有一個value值
2.1.3 若是 promise 在 rejected 狀態
    2.1.3.1 不會變成其它狀態
    2.1.3.2 必須有一個 promise 被 reject 的 reason

2.2 then 方法

promise 必須提供一個 then 方法,來訪問最終的結果編程

promisethen 方法接收兩個參數promise

promise.then(onFulfilled, onRejected)
2.2.1 onFulfilled 和 onRejected 都是可選參數:
    2.2.1.1 onFulfilled 必須是函數類型
    2.2.1.2 onRejected 必須是函數類型
2.2.2 若是 onFulfilled 是函數:
    2.2.2.1 必須在 promise 變成 fulfilled 時,調用 onFulfilled,參數是 promise 的 value
    2.2.2.2 在 promise 的狀態不是 fulfilled 以前,不能調用
    2.2.2.3 onFulfilled 只能被調用一次
2.2.3 若是 onRejected 是函數:
    2.2.3.1 必須在promise變成 rejected 時,調用 onRejected,參數是promise的reason
    2.2.3.2 在promise的狀態不是 rejected 以前,不能調用
    2.2.3.3 onRejected 只能被調用一次
2.2.4 onFulfilled 和 onRejected 應該是微任務
2.2.5 onFulfilled 和 onRejected 必須做爲函數被調用
2.2.6 then 方法可能被屢次調用
    2.2.6.1 若是 promise 變成了 fulfilled 態,全部的 onFulfilled 回調都須要按照 then 的順序執行
    2.2.6.2 若是 promise 變成了 rejected 態,全部的 onRejected 回調都須要按照 then 的順序執行
2.2.7 then 必須返回一個 promise, promise2 = promise1.then(onFulfilled, onRejected);
    2.2.7.1 onFulfilled 或 onRejected 執行的結果爲 x, 執行 resolutionProcedure(promise2, x) 
    2.2.7.2 若是 onFulfilled 或者 onRejected 執行時拋出異常e, promise2 須要被 reject
    2.2.7.3 若是 onFulfilled 不是一個函數,promise2 以 promise1 的值 fulfilled
    2.2.7.4 若是 onRejected 不是一個函數,promise2 以 promise1 的 reason rejected

2.3 The Promise Resolution Procedure

resolutionProcedure(promise2, x, resolve, reject)
2.3.1 若是 promise2 和 x 相等,那麼 promise 執行 reject TypeError
2.3.2 若是 x 是一個 promsie
    2.3.2.1 若是 x 是 pending 狀態,那麼 promise 必需要保持 pending 狀態直到 x 變成 fulfilled 或者 rejected 狀態
    2.3.2.2 若是 x 是 fulfilled 狀態, fulfill promise with the same value
    2.3.2.3 若是 x 是 rejected 狀態, reject promise with the same reason
2.3.3 若是 x 是一個 object 或者 是一個 function
    2.3.3.1 let then = x.then.
    2.3.3.2 若是 x.then 這步出錯,那麼 reject promise with e as the reason
    2.3.3.3 若是 then 是一個函數,then.call(x, resolvePromise, rejectPromise)
        2.3.3.3.1 resolvePromiseFn 的 入參是 y, 執行 resolutionProcedure(promise2, y, resolve, reject);
        2.3.3.3.2 rejectPromise 的 入參是 r, reject promise with r.
        2.3.3.3.3 若是 resolvePromise 和 rejectPromise 都調用了,那麼第一個調用優先,後面的調用忽略。
        2.3.3.3.4 若是調用then拋出異常 e
            2.3.3.3.4.1 若是 resolvePromise 或 rejectPromise 已經被調用,那麼忽略
            2.3.3.3.4.2 不然,reject promise with e as the reason
    2.3.3.4 若是 then 不是一個function. fulfill promise with x.
2.3.4 若是 x 不是一個 object 或者 function,fulfill promise with x.

Promise 源碼實現

接下來咱們只要按照規範來實現 Promise 就好了,很顯然規範後半部分看起來是比較複雜的,尤爲是 resolutionProcedure 函數的實現,但其實這只是一些實現的細節而已。初看可能不是那麼順暢,那麼強烈建議多看幾遍規範,而後本身多實現幾遍。緩存

  1. promise 須要傳遞一個 executor 執行器,執行器馬上執行
  2. 執行器 executor 接受兩個參數,分別是 resolvereject
  3. promise 只能從 pendingrejected, 或者從 pendingfulfilled,狀態一旦確認,就不會再改變
  4. promise 都有 then 方法,then 接收兩個參數,分別是 promise 成功的回調 onFulfilled, 和 promise 失敗的回調 onRejected
  5. 若是調用 then 時,promise 已經成功,則執行 onFulfilled,並將 promise 的值做爲參數傳遞進去。 若是 promise 已經失敗,那麼執行 onRejected, 並將 promise 失敗的緣由做爲參數傳遞進去。若是 promise 的狀態是 pending,須要將 onFulfilledonRejected 函數存放起來,等待狀態肯定後,再依次將對應的函數執行。
  6. then 的參數 onFulfilledonRejected 能夠缺省
  7. promise 能夠 then 屢次,promisethen 方法返回一個新的 promise
  8. 若是 then 返回的是一個結果,那麼就會把這個結果做爲參數,傳遞給下一個 then 的成功的回調 onFulfilled
  9. 若是 then 中拋出了異常,那麼就會把這個異常做爲參數,傳遞給下一個 then 的失敗的回調 onRejected
  10. 若是 then 返回的是一個 promise, 那麼須要等這個 promise,那麼會等這個 promise 執行完。promise 若是成功, 就走下一個 then 的成功,若是失敗,就走下一個 then 的失敗
// 定義三種狀態的常量
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

// pomise 接收一個 executor 執行器,執行器馬上執行
function MyPromise(executor) {
  const _this = this;
  // promise 當前的狀態
  _this.currentState = PENDING;
  _this.value = undefined;

  // 保存 then 中的回調,只有當 promise 狀態爲 pending 時纔會緩存,而且每一個實例至多緩存一個
  _this.onFulfilledCallbacks = [];
  _this.onRejectedCallbacks = [];


  function resolve(value) {
    if (value instanceof MyPromise) {
      // 若是 value 是個 Promise,調用 then 方法繼續執行
      value.then(resolve, reject);
    }

    // 異步執行,保證執行順序
    setTimeout(() => {
      if (_this.currentState === PENDING) {
        _this.currentState = FULFILLED;
        _this.value = value;
        _this.onFulfilledCallbacks.forEach(fn => fn());
      }
    });
  }

  function reject(reason) {
    // 異步執行,保證執行順序
    setTimeout(() => {
      if (_this.currentState === PENDING) {
        _this.currentState = REJECTED;
        _this.value = reason;
        _this.onRejectedCallbacks.forEach(fn => fn());
      }
    });
  }

  try {
    executor(resolve, reject);
  } catch(err) {
    reject(err);
  }
}

MyPromise.prototype.constructor = MyPromise;

MyPromise.prototype.then = function (onFulfilled, onRejected) {
  const _this = this;
  // 2.2.1 onFulfilled 和 onRejected 都是可選參數
  // 2.2.5 onFulfilled 和 onRejected 必須做爲函數被調用
  // 2.2.7.3 若是 onFulfilled 不是一個函數,promise2 以promise1的值fulfilled
  // 2.2.7.4 若是 onRejected 不是一個函數,promise2 以promise1的reason rejected
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : valve => valve;
  onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };

  // 2.2.7,then 必須返回一個新的 promise
  const promise2 = new MyPromise((resolve, reject) => {
    if (_this.currentState === FULFILLED) {
      // 2.2.4 保證 onFulfilled,onRjected 異步執行
      setTimeout(() => {
        try {
          // 2.2.7.1 onFulfilled 或 onRejected 執行的結果爲 x, 調用 resolutionProcedure
          const x = onFulfilled(_this.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch(err) {
          // 2.2.7.2 若是 onFulfilled 或者 onRejected 執行時拋出異常 err, promise2 須要被 reject
          reject(err);
        }
      });
    }
    
    if (_this.currentState === REJECTED) {
      setTimeout(() => {
        try {
          // 2.2.7.1 onFulfilled 或 onRejected 執行的結果爲 x, 調用 resolutionProcedure
          const x = onRejected(_this.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch(err) {
          // 2.2.7.2 若是 onFulfilled 或者 onRejected 執行時拋出異常 err, promise2 須要被 reject
          reject(err);
        }
      });
    }
    
    if (_this.currentState === PENDING) {
      _this.onFulfilledCallbacks.push(() => {
        setTimeout(() => {
          try {
            const x = onFulfilled(_this.value);
            resolutionProcedure(promise2, x, resolve, reject);
          } catch(err) {
            reject(err);
          }
        });
      });

      _this.onRejectedCallbacks.push(() => {
        setTimeout(() => {
          try {
            const x = onRejected(_this.value);
            resolutionProcedure(promise2, x, resolve, reject);
          } catch(err) {
            reject(err);
          }
        });
      });
    }
  });

  return promise2;
}

// 2.3 resolutionProcedure(promise2, x, resolve, reject)
function resolutionProcedure(promise2, x, resolve, reject) {

  // 2.3.1 若是 promise2 和 x 相等,那麼 reject promise with a TypeError
  if (promise2 === x) {
    reject(new TypeError('Error'));
  }

  // 2.3.2 若是 x 爲 Promise,狀態爲 pending 須要繼續等待不然執行
  if (x instanceof MyPromise) {
    if (x.currentState === PENDING) {
      x.then(value => {
        resolutionProcedure(promise2, value, resolve, reject);
      }, reject)
    } else {
      x.then(resolve, reject);
    }
  }

  // 2.3.3.3.3 reject 或者 resolve 其中一個執行過的話,忽略其餘的
  let called = false;

  // 2.3.3,判斷 x 是否爲對象或者函數
  if ( x && (typeof x === 'object' || typeof x === 'function') ) {
    try {
      let then = x.then;
      // 2.3.3.3 若是 then 是一個函數,then.call(x, resolvePromise, rejectPromise)
      if (typeof then === 'function') {
        then.call(
          x,
          y => {
            if (called) return;
            called = true;
            // 2.3.3.3.1 resolvePromiseFn 的 入參是 y, 執行 resolutionProcedure(promise2, y, resolve, reject);
            resolutionProcedure(promise2, y, resolve, reject);
          },
          r => {
            if (called) return;
            called = true;
            // 2.3.3.3.2 rejectPromise 的 入參是 r, reject promise with r.
            reject(r);
          }
        );
      } else {
        // 2.3.3.4 若是 then 不是一個 function. fulfill promise with x.
        resolve(x);
      }
    } catch(err) {
      // 2.3.3.2 若是 x.then 這步出錯,那麼 reject promise with err as the reason
      if (called) return;
      called = true;
      reject(err)
    }

  } else {
    // 2.3.4 若是 x 不是一個 object 或者 function,fulfill promise with x.
    resolve(x);
  }
}

module.exports = MyPromise;

測試

promises-aplus-tests 這個 npm 包能夠幫助咱們測試所編寫的 promise 代碼是否符合 Promises/A+ 的規範。微信

不過咱們須要先增長如下代碼去提供測試的接口異步

MyPromise.defer = MyPromise.deferred = function () {
  let dfd = {};
  dfd.promise = new MyPromise((resolve, reject) => {
    dfd.resolve = resolve;
    dfd.reject = reject;
  });
  return dfd;
}

經過 npm 安裝async

npm install promises-aplus-tests --save-dev

經過執行 promises-aplus-tests target.js 能夠執行測試,promises-aplus-tests 中共有 872 條測試用例。

能夠在個人 github 進行查看,經過 npm run test 來執行測試,能夠經過全部的測試用例。

更多精彩內容,歡迎關注微信公衆號~

相關文章
相關標籤/搜索