手擼基於ES6 的PromiseA+ 規範實現 Promise

本文引用文檔地址javascript

Promise 到底解決了什麼問題?

你們都知道 Promise 解決了回調地獄的問題,可是到底什麼是回調地獄? 他到底哪兒have some wrong ? 是由於嵌套回調仍是,仍是回調執行順序問題,仍是不夠美觀?html

回調地獄

來腦補一下,實現一個說話的需求,說完才能說下一句,那麼很容易的寫出下面這樣的一段代碼java

function say (word, callback) {
  console.log(word)
  setTimeout(() => {
    callback&&callback()
  },1000)
}
複製代碼

ok! fine!! 那麼如今要說話了node

say("first", function () {
  say("second", function () {
    say("third", function () {
      console.log("end");
    });
  });
});
// first second third end
複製代碼

在這個例子中的嵌套的問題僅僅是縮進的問題,而縮進除了會讓代碼變寬可能會形成讀代碼的一點不方便以外,並無什麼其餘的問題。若是僅僅是這樣,爲何不叫「縮進地獄」或「嵌套地獄」?git

把回調地獄徹底理解成縮進的問題是常見的對回調地獄的誤解。要回到「回調地獄」這個詞語上面來,它的重點就在於「回調」,而「回調」在JS中應用最多的場景固然就是異步編程了。程序員

因此,「回調地獄」所說的嵌套實際上是指異步的嵌套。它帶來了兩個問題:可讀性的問題和信任問題es6

可讀性問題

很容易找到一個這樣執行順序問題的問題github

for (var i = 0; i < 5; i++) {
  setTimeout(function() {
    console.log(new Date, i);
  }, 1000);
}

console.log(new Date, i);
複製代碼

很好,結果不那麼重要,可是你總得想那麼一會吧。想代碼的執行順序問題,那麼你必須熟知異步代碼的執行機制,還要能很快的分析出想要的結果,ajax

so 你能作到麼。npm

信任問題

好的,讓咱們接着腦補一下,你是華夏某銀行的少主,你的一個程序員寫下了一段代碼,調用了三方的ajax 扣款請求,可是因爲三方庫機制問題,調用了屢次。

那麼,你能很好的信任回調麼。還會不會有其餘的信任問題發生呢。

so

  • 回調時機不對(早/晚)
  • 屢次執行回調
  • 成功失敗同時執行

...

實際問題在於控制權轉交給了第三方,不可控的執行致使了咱們最頭疼的信任問題,實際在開發中,咱們須要檢查的狀態更多,依賴更多。信任問題帶來的成本,就大大致使了可讀性下降,須要更多的代碼來check。

雖然這些 fail 出現的機率並不高,可能在你編碼的時候都沒有關注到,可是實際上卻須要不少臃腫的代碼去強壯他,這就不夠友好了。

實際上萬能的開發者們也想了不少方法來解決信任問題

  • for example
// 增長失敗回調
function sendAjax(onSuccess,onFail) {
  if(success) {
    onSuccess&&onSuccess()
  }else{
    onFail&&onFail()
  }
}
複製代碼
  • example again
// node error first
fs.stat(file, (err, stat) => {
  if (err) {
    doSomeThing()
  } else {
    doOtherThing()
  }
})
複製代碼

實際上這並無解決全部的問題,諸如回調屢次執行,複雜場景下的回調問題。

小總結 異步回調帶來的問題主要集中在 可讀性差/信任問題

Promise 如何解決這些問題

介紹

首先咱們來看一下 PromiseA+ 規範的第一句話

An open standard for sound, interoperable JavaScript promises—by implementers, for implementers.

一個開放的標準,用於實施者對實施者的聲音,可互操做的JavaScript保證。

A promise represents the eventual result of an asynchronous operation. The primary way of interacting with a promise is through its then method, which registers callbacks to receive either a promise’s eventual value or the reason why the promise cannot be fulfilled.

一個promise表示異步操做的最終結果。 與諾言互動的主要方式是經過then方法,該方法註冊回調以接收諾言的最終值或諾言沒法實現的緣由。

This specification details the behavior of the then method, providing an interoperable base which all Promises/A+ conformant promise implementations can be depended on to provide. As such, the specification should be considered very stable. Although the Promises/A+ organization may occasionally revise this specification with minor backward-compatible changes to address newly-discovered corner cases, we will integrate large or backward-incompatible changes only after careful consideration, discussion, and testing.

該規範詳細說明了then方法的行爲,提供了一個可互操做的基礎,全部Promises / A +符合諾言的實現均可以依靠該基礎來提供。 所以,該規範應被視爲很是穩定。 儘管Promises / A +組織有時會經過向後兼容的微小更改來修訂此規範,以解決新發現的極端狀況,但只有通過仔細考慮,討論和測試以後,咱們纔會集成大型或向後不兼容的更改。

1.0 Terminology 概念術語

  • promise is an object or function with a then method whose behavior conforms to this specification.
  • thenable is an object or function that defines a then method.
  • value is any legal JavaScript value (including undefined, a thenable, or a promise).
  • exception is a value that is thrown using the throw statement.
  • reason is a value that indicates why a promise was rejected.

2.0 Requirements 要求 (實現)

對於建立一個可互操做的javascript保證的標準來講,要解決的問題無非就是狀態管理,註冊回調事件,和可靠的承諾解決體系

**PromiseA+**規範實際上就從這三點出發,定義了三種標準。

那麼咱們來開始寫咱們的代碼,基於es6 class

class Promise {
  constructor(executor) {
    // PromiseA+...
  }
}
module.exports = Promise;
複製代碼

2.1Promise States

A promise must be in one of three states: pending, fulfilled, or rejected.

一個Promise 必須處於pending``fulfilled``rejected三種狀態之間

  • 2.1.1When pending, a promise:
    • 2.1.1.1may transition to either the fulfilled or rejected state.
  • 2.1.2When fulfilled, a promise:
    • 2.1.2.1must not transition to any other state.
    • 2.1.2.2must have a value, which must not change.
  • 2.1.3When rejected, a promise:
    • 2.1.3.1must not transition to any other state.
    • 2.1.3.2must have a reason, which must not change.

Here, 「must not change」 means immutable identity (i.e. ===), but does not imply deep immutability.

這裏實際上定義了Promise 的狀態轉換關係,定義了

  • 初始pending狀態,能夠轉換爲其餘兩個狀態
  • 處於成功失敗狀態不可以轉換到其餘狀態,必須有對應的成功值value or 失敗緣由reason
  • 失敗狀態緣由引用不可更改

so!!! do it

class Promise {
  constructor(executor) {
    // 定義初始化狀態常量
    this.PENDING = 'pending';//初始態
    this.FULFILLED = 'fulfilled';//初始態
    this.REJECTED = 'rejected';//初始態


    // PromiseA+ 2.1.1.1
    // 初始化狀態
    this.status = this.PENDING;

    // PromiseA+ 2.1
    // 定義緩存經過then註冊的成功失敗回調的數組,支持 then 方法註冊多個回調
    this.onResolveCallbacks = [];
    this.onRejectCallbacks = [];

    // 緩存this,避免this指向問題致使bug
    const self = this;

    // 定義成功失敗方法,做爲Promise 傳入的函數體的參數
    // 實現PromiseA+狀態轉換 定義成功失敗參數
    function reject(v) {
      // Here, 「must not change」 means immutable identity (i.e. ===), but does not imply deep immutability.
      const reason = v;
      //PromiseA+ 2.1.3
      if (self.status === self.PENDING) {
        self.status = self.REJECTED;
        self.value = reason;
        self.onRejectCallbacks.forEach(item => item(self.value));
      }
    }
    function resolve(value) {
      //PromiseA+ 2.1.2
      if (self.status === self.PENDING) {
        self.status = self.FULFILLED;
        self.value = value;
        self.onResolveCallbacks.forEach(item => item(self.value));
      }
    }

    // 開始執行函數體,捕獲錯誤。執行報錯則直接拒絕Promise
    try {
      executor(resolve, reject);
    } catch (error) {
      reject(error);
    }


  }
}
module.exports = Promise;
複製代碼

new Promise過程已經實現了

2.2The then Method

A promise must provide a then method to access its current or eventual value or reason.

A promise’s then method accepts two arguments:

promise.then(onFulfilled, onRejected)
複製代碼

那麼在類上定義方法

class Promise {
  constructor(executor) {
    ...
  }
  
  // PromiseA+ 2.2 // PromiseA+ 2.2.6
  then(onFulfilled, onRejected) {

  }
}
module.exports = Promise;
複製代碼
  • 2.2.1 Both onFulfilled and onRejected are optional arguments:

    • 2.2.1.1 If onFulfilled is not a function, it must be ignored.
    • 2.2.1.2 If onRejected is not a function, it must be ignored.
  • 2.2.2 If onFulfilled is a function:

    • 2.2.2.1 it must be called after promise is fulfilled, with promise’s value as its first argument.
    • 2.2.2.2 it must not be called before promise is fulfilled.
    • 2.2.2.3 it must not be called more than once.
  • 2.2.3 If onRejected is a function,

    • 2.2.3.1 it must be called after promise is rejected, with promise’s reason as its first argument.
    • 2.2.3.2 it must not be called before promise is rejected.
    • 2.2.3.3 it must not be called more than once.
  • 成功/失敗回調必須是一個funciton,不是函數將被忽略
  • 成功/失敗回調必須在 成功/失敗 以後被調用,第一個參數必須是value/reason
  • 成功/失敗回調不能調用屢次
  • 2.2.4onFulfilled or onRejected must not be called until the execution context stack contains only platform code. [3.1].

成功/失敗回調 不能在Promise 平臺代碼執行完以前被調用,意義在於防止調用時還有回調沒有被註冊進來

  • 2.2.5onFulfilled and onRejected must be called as functions (i.e. with no this value). [3.2]

成功/失敗回調 必須做爲函數被調用,若是拿到的參數不是函數,忽略它,生成默認的同步執行函數,以相同的值執行後續回調

  • 2.2.6then may be called multiple times on the same promise.
    • 2.2.6.1If/when promise is fulfilled, all respective onFulfilled callbacks must execute in the order of their originating calls to then.
    • 2.2.6.2If/when promise is rejected, all respective onRejected callbacks must execute in the order of their originating calls to then.
  • then 方法可能被同一個promise 調用屢次
  • then 方法註冊的成功/失敗回調必須被以註冊的順序執行
  • 2.2.7then must return a promise [3.3].
    • 2.2.7.1If either onFulfilled or onRejected returns a value x, run the Promise Resolution Procedure [[Resolve]](promise2, x).
    • 2.2.7.2If either onFulfilled or onRejected throws an exception e, promise2 must be rejected with e as the reason.
    • 2.2.7.3If onFulfilled is not a function and promise1 is fulfilled, promise2 must be fulfilled with the same value as promise1.
    • 2.2.7.4If onRejected is not a function and promise1 is rejected, promise2 must be rejected with the same reason as promise1.
promise2 = promise1.then(onFulfilled, onRejected);
複製代碼
  • then 方法必須返回一個Promise instance
  • promise1 成功或則失敗回調正確執行拿到一個返回值value x 運行Promise Resolution Procedure解析程序[Resolve]](promise2, x)
  • promise1 拒絕throws an exception e ,就以相同的緣由拒絕promise2
  • promise1成功/失敗回調 不是一個function 並且promise1 成功/失敗時,promise2必須以相同的value/e 被成功或者拒絕

so!!! do it!!!

PromiseA+ 2.2.4 onFulfilled or onRejected must not be called until the execution context stack contains only platform code.

爲了防止在平臺代碼執行完畢,徹底註冊回調以前調用回調,採用宏任務setTimeout0實現 ::: details 展開查看constructor

constructor(executor) {
  this.PENDING = 'pending';//初始態
  this.FULFILLED = 'fulfilled';//初始態
  this.REJECTED = 'rejected';//初始態
  //PromiseA+ 2.1.1.1
  this.status = this.PENDING;

  // PromiseA+ 2.1
  this.onResolveCallbacks = [];
  this.onRejectCallbacks = [];


  const self = this;
  function reject(v) {
    const reason = v;
    // PromiseA+ 2.2.4
    setTimeout(() => {
      //PromiseA+ 2.1.3
      if (self.status === self.PENDING) {
        self.status = self.REJECTED;
        self.value = reason;
        // console.dir(self);
        // console.log('------self--------------------------------');
        self.onRejectCallbacks.forEach(item => item(self.value));
      }
    });

  }
  function resolve(value) {
    // PromiseA+ 2.2.4
    setTimeout(() => {
      //PromiseA+ 2.1.2
      if (self.status === self.PENDING) {
        self.status = self.FULFILLED;
        self.value = value;
        self.onResolveCallbacks.forEach(item => item(self.value));
      }
    });
  }
  try {
    executor(resolve, reject);
  } catch (error) {
    reject(error);
  }
}
複製代碼

:::

class Promise {
  constructor(executor) {
    ...
  }
  

  resolvePromise(promise2, x, resolve, reject) {

  }

  // PromiseA+ 2.2 // PromiseA+ 2.2.6
  then(onFulfilled, onRejected) {
    //緩存this
    const self = this;
    //PromiseA+ 2.2.1 / PromiseA+ 2.2.5 / PromiseA+ 2.2.7.3 / PromiseA+ 2.2.7.4
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : x => x;
    onRejected = typeof onRejected === 'function' ? onRejected : e => { throw e; };

    let promise2;

    function fulfillCallback(resolve, reject) {
      // PromiseA+ 2.2.4
      setTimeout(() => {
        try {
          const x = onFulfilled(self.value);
          //PromiseA+ 2.2.7.1
          self.resolvePromise(promise2, x, resolve, reject);
        } catch (error) {
          //PromiseA+ 2.2.7.2
          reject(error);
        }
      });
    }
    function rejectCallback(resolve, reject) {
      // PromiseA+ 2.2.4
      setTimeout(() => {
        try {
          const e = onRejected(self.value);
          //PromiseA+ 2.2.7.1
          self.resolvePromise(promise2, e, resolve, reject);
        } catch (error) {
          //PromiseA+ 2.2.7.2
          reject(error);
        }
      });
    }

    // PromiseA+ 2.2.2
    if (self.status === self.FULFILLED) {
      //PromiseA+ 2.2.7
      return promise2 = new Promise((resolve, reject) => {
        fulfillCallback(resolve, reject);
      });

    }
    // PromiseA+ 2.2.3
    if (self.status === self.REJECTED) {
      //PromiseA+ 2.2.7
      return promise2 = new Promise((resolve, reject) => {
        rejectCallback(resolve, reject);
      });
    }
    if (self.status === self.PENDING) {
      //PromiseA+ 2.2.7
      return promise2 = new Promise((resolve, reject) => {
        self.onResolveCallbacks.push(() => {
          fulfillCallback(resolve, reject);
        });

        self.onRejectCallbacks.push(() => {
          rejectCallback(resolve, reject);
        });
      });
    }
  }
}
module.exports = Promise;
複製代碼

so 2.2.7.1 是個什麼鬼!!鬼!!鬼啊!!!!

接着看下去吧

2.3The Promise Resolution Procedure

::: danger 交互性 javascript 保證 If either onFulfilled or onRejected returns a value x, run the Promise Resolution Procedure [[Resolve]](promise2, x).

promise1 成功或則失敗回調正確執行拿到一個返回值value x 運行Promise Resolution Procedure解析程序[Resolve]](promise2, x) :::

The promise resolution procedure is an abstract operation taking as input a promise and a value, which we denote as [[Resolve]](promise, x). If x is a thenable, it attempts to make promise adopt the state of x, under the assumption that x behaves at least somewhat like a promise. Otherwise, it fulfills promise with the value x.

This treatment of thenables allows promise implementations to interoperate, as long as they expose a Promises/A+-compliant then method. It also allows Promises/A+ implementations to 「assimilate」 nonconformant implementations with reasonable then methods.

promise resolution procedurepromise 解決程序其實是一種承諾實現的抽象,將promise 和值x 做爲輸入,表示爲[[Resolve]](promise,x) 若是x是可能的,則在x的行爲至少相似於承諾的假設下,嘗試使承諾採用x的狀態。 不然,它將以值x履行承諾。

這種對可實現對象的處理使答應實現能夠互操做,只要它們公開了符合Promises / A +的then方法便可。 它還容許Promises / A +實現使用合理的then方法「整合」不合格的實現。

想要運行 promise resolution procedure, 須要遵循瞎下面的規範。

  • 2.3.1 If promise and x refer to the same object, reject promise with a TypeError as the reason.

若是promisex指向同一個對象,以一個TypeError做爲緣由拒絕promise

  • 2.3.2 If x is a promise, adopt its state [3.4]:
    • 2.3.2.1 If x is pending, promise must remain pending until x is fulfilled or rejected.
    • 2.3.2.2 If/when x is fulfilled, fulfill promise with the same value.
    • 2.3.2.3 If/when x is rejected, reject promise with the same reason.

若是x是一個promise(是本身的實例instanceof) 採用下面的狀態

  • 若是xpending狀態,promise2必須保持pending狀態直到xfulfilled/rejected
  • 若是xfulfilled/rejectedpromise2 必須保持 x 相同的 value/reasonfulfilled/rejected
  • 2.3.3 Otherwise, if x is an object or function,

    • 2.3.3.1 Let then be x.then. [3.5]

    • 2.3.3.2 If retrieving the property x.then results in a thrown exception e, reject promise with e as the reason.

    • 2.3.3.3 If then is a function, call it with x as this, first argument resolvePromise, and second argument rejectPromise, where:

      • 2.3.3.3.1 If/when resolvePromise is called with a value y, run [[Resolve]](promise, y).

      • 2.3.3.3.2 If/when rejectPromise is called with a reason r, reject promise with r.

      • 2.3.3.3.3 If both resolvePromise and rejectPromise are called, or multiple calls to the same argument are made, the first call takes precedence, and any further calls are ignored.

      • 2.3.3.3.4 If calling then throws an exception e,

        • 2.3.3.3.4.1 If resolvePromise or rejectPromise have been called, ignore it.
        • 2.3.3.3.4.2 Otherwise, reject promise with e as the reason.
    • 2.3.3.4 If then is not a function, fulfill promise with x.

  • 2.3.3.4 If x is not an object or function, fulfill promise with x.

x 是一個object or function 此時多是一個同步的處理函數,也多是一個thenable 對象

  • 訪問xthen 屬性 實際上該操做可能會報錯,通常來講訪問一個對象的屬性不會報錯,可是若是該屬性是一個 getter 的時候,在執行getter 的時候可能會拋異常e。此時應該以e 來拒絕peomise2
  • then 是一個function,經過then.call(x)調用它,同時給x註冊成功處理函數和失敗處理函數,
    • 當成功回調被執行並傳入y的時候,運行[[Resolve]](promise, y) 繼續解析。
    • 當失敗回調被執行並傳入e的時候,把e做爲reason拒絕promise2
  • 若是成功失敗回調被屢次調用,那麼第一次的調用將優先調用,其餘的調用將被忽略,這裏須要添加called 標誌是否被調用,在每次調用成功失敗時校驗,並調用時立馬修改標誌位狀態
  • 若是x不是function 對象那麼以x實現promise
  • 若是x不是thenable 對象那麼以x實現promise

解析程序實際上保證了promise 的可靠性,對thenable對象狀態的判斷,循環解析,直到x做爲一個普通的不能在被解析的非thenable才實現調用,對錯誤的處理也貫徹整個流程,並且保證了調用的惟一性。 這實現了那句可互操做的JavaScript保證。

那麼,讓咱們來一步一步的實現它吧

resolvePromise(promise2, x, resolve, reject) {
  const self = this;
  // PromiseA+ 2.3.1
  if (promise2 === x) { return reject(new TypeError('循環引用')); }


  // PromiseA+ 2.3.2
  if (x instanceof Promise) {
    if (x.status === self.PENDING) {
      // PromiseA+ 2.3.2.1
      x.then(function(y) { self.resolvePromise(promise2, y, resolve, reject); }, reject);
    } else {
      // PromiseA+ 2.3.2.2 /PromiseA+ 2.3.2.3
      x.then(resolve, reject);
    }
    // PromiseA+ 2.3.3
  } else if (x && ((typeof x === 'object') || (typeof x === 'function'))) {
    // PromiseA+ 2.3.3.3.3 / PromiseA+ 2.3.3.3.4.1
    let called = false;
    try {
      // PromiseA+ 2.3.3.1
      const then = x.then;
      // PromiseA+ 2.3.3.3
      if (typeof then === 'function') {
        try {
          then.call(
            x,
            function(y) {
              if (called) return;
              called = true;
              // PromiseA+ 2.3.3.3.1
              self.resolvePromise(promise2, y, resolve, reject);
            },
            function(e) {
              if (called) return;
              called = true;
              // PromiseA+ 2.3.3.3.2
              reject(e);
            }
          );
        } catch (e) {
          if (called) return;
          called = true;
          // PromiseA+ 2.3.3.3.2
          reject(e);
        }

      } else {
        // PromiseA+ 2.3.3.4
        resolve(x);
      }

    } catch (error) {
      if (called) return;
      called = true;
      // PromiseA+ 2.3.3.2 / PromiseA+ 2.3.3.4.2
      reject(error);
    }
  } else {
    // PromiseA+ 2.3.4
    resolve(x);
  }
}
複製代碼

Promise.all/race/resolve/reject

到此PromiseA+規範已經徹底實現,原則上全部的Promise 庫都應該遵循此規範,現代瀏覽器支持的promise 都支持一些Promise.allPromise.racePromise.resolvePromise.reject,接下來讓咱們來實現它

//all 實現
Promise.all = function(promises) {
  //promises是一個promise的數組
  return new Promise(function (resolve, reject) {
    const arr = []; //arr是最終返回值的結果
    let i = 0; // 表示成功了多少次
    function processData(index, y) {
      arr[index] = y;
      if (++i === promises.length) {
        resolve(arr);
      }
    }
    for (let i = 0; i < promises.length; i++) {
      promises[i].then(function (y) {
        processData(i, y);
      }, reject);
    }
  });
};

// race 實現
Promise.race = function (promises) {
  return new Promise(function (resolve, reject) {
    for (let i = 0; i < promises.length; i++) {
      promises[i].then(resolve, reject);
    }
  });
};

// Promise.resolve 實現
Promise.resolve = function (value) {
  return new Promise(function(resolve, reject) {
    resolve(value);
  });
};

// Promise.reject 實現
Promise.reject = function (reason) {
  return new Promise(function(resolve, reject) {
    reject(reason);
  });
};
複製代碼

Promise 規範測試

Promises/A+ 一致化測試套件

usage

npm install promises-aplus-tests -g
複製代碼

測試套件其實是一個cli命令行工具,只須要在Promise 上暴露出一個fucntion接口deferred,函數返回一個對象,對象包含一個Promise 實例,和實例的resolve,reject 參數

Adapters In order to test your promise library, you must expose a very minimal adapter interface. These are written as Node.js modules with a few well-known exports:

  • resolved(value): creates a promise that is resolved with value.
  • rejected(reason): creates a promise that is already rejected with reason.
  • deferred(): creates an object consisting of { promise, resolve, reject }:
    • promise is a promise that is currently in the pending state.
    • resolve(value) resolves the promise with value.
    • reject(reason) moves the promise from the pending state to the rejected state, with rejection reason reason.
Promise.deferred = function () {
  const defer = {};
  defer.promise = new Promise(function (resolve, reject) {
    defer.resolve = resolve;
    defer.reject = reject;
  });
  return defer;
};
複製代碼

進入到Promise.js所在目錄,運行

promises-aplus-tests ./Promise.js
複製代碼

or in package.json

"scripts": {
  "testa:promise": "promises-aplus-tests ./src/promise/Promise.js"
},
複製代碼

那麼順利的話! 你講看到這個

相關文章
相關標籤/搜索