捋捋Promise

本文原創:duxiaoxuehtml

一些引用

一旦一個 Promise 決議,不管是如今仍是未來,下一個步驟老是相同的。git

既然 Promise 是經過 new Promise(..) 語法建立的,那你可能就認爲能夠經過 p instanceof Promise 來檢查。但遺憾的是,這並不足以做爲檢查方法。識別 Promise(或者行爲相似於 Promise 的東西)就是定義某種稱爲 thenable 的東西,將其定義爲任何具備 then(..) 方法的對象和函數。咱們認爲,任何這樣的值就是 Promise 一致的 thenable。es6

Promise 模式構建的可能最重要的特性:信任。github

即便是當即完成的 Promise(相似於 new Promise(function(resolve){ resolve(42); }))也沒法被同步觀察到。也就是說,對一個 Promise 調用 then(..) 的時候,即便這個 Promise 已經決議,提供給 then(..) 的回調也總會被異步調用。編程

一個 Promise 決議後,這個 Promise 上全部的經過 then(..) 註冊的回調都會在下一個異步時機點上依次被當即調用。這些回調中的任意一個都沒法影響或延誤對其餘回調的調用。promise

-- YDKJS安全

先說異步

所謂"異步",簡單說就是一個任務不是連續完成的,能夠理解成該任務被人爲分紅兩段,先執行第一段,而後轉而執行其餘任務,等作好了準備,再回過頭執行第二段。 ... 相應地,連續的執行就叫作同步。因爲是連續執行,不能插入其餘任務,因此操做系統從硬盤讀取文件的這段時間,程序只能乾等着。異步

--阮一峯《ES6入門》異步編程

在 Promise 以前:回調函數

setTimeout(() => {
    // statements
}, 1000)
複製代碼
var xhr = new XMLHttpRequest(),
    method = "GET",
    url = "https://developer.mozilla.org/";

xhr.open(method, url, true);
xhr.onreadystatechange = function () {
    if(xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
        console.log(xhr.responseText);
    }
};
xhr.send();
複製代碼

缺點:函數

  • 大量的嵌套致使的回調地獄讓人難以理解代碼的實際發生順序
  • 控制反轉產生信任問題,回調執行狀況沒法保證

大腦對於事情的計劃方式是線性的、阻塞的、單線程的語義,可是回調錶達異步流程的方式是非線性的、非順序的,這使得正確推導這樣的代碼難度很大。 回調函數的調用控制交與第三方函數內部,沒法保證回調函數必定會被正確調用。可能出現不少異常狀況:所需參數傳遞錯誤、調用回調過早或過晚、調用回調次數太多或太少、吞掉可能出現的錯誤與異常等等。 回調函數是 JavaScript 異步的基本單元。可是隨着 JavaScript 愈來愈成熟,對於異步編程領域的發展,回調已經不夠用了。 ... 咱們須要一種更同步、更順序、更阻塞的的方式來表達異步,就像咱們的大腦同樣。 也須要一個通用方案來解決信任問題。

-- YDKJS

Promise登場

promise的思想

若是咱們不把本身程序的 continuation 傳給第三方,而是但願第三方給咱們提供瞭解其任務什麼時候結束的能力,而後由咱們本身的代碼來決定下一步作什麼。這種範式就稱爲Promise

Promise 決議後就是外部不可變的值,咱們能夠安全地把這個值傳遞給第三方,並確信它不會被有意無心地修改。特別是對於多方查看同一個 Promise 決議的狀況,尤爲如此。一方不可能影響另外一方對 Promise 決議的觀察結果。 不可變性聽起來彷佛一個學術話題,但實際上這是 Promise 設計中最基礎和最重要的因素。

Promise 是一種封裝和組合將來值的易於複用的機制。

-- YKDJS

社區推進

最先的Promise是由社區首先提出和實現的,早期比較有名的有jQuery的Deferred對象、bluebird、Q等等。ES6也將Promise歸入語言標準,提供了原生的Promise對象。

最新的規範是在2014年發佈的promise/A+

Promise/A+規範

一個promise指示一個異步操做的最終結果。與promise交互的主要方法是經過它的then方法,在then方法中註冊了接收promise的最終值或是沒法被完成緣由的回調。

Promise/A+只專一於提供可操做的then方法的規範。

規範中指出,ECMAScript語言規範中的Promise對象基於本規範還實現了許多額外的要求,也就是說咱們能夠自行實現一個徹底遵照promise/A+規範但沒必要徹底遵照ECMAScript語言規範的Promise,某種程度上說ES6裏面的Promise也只是許多promise/A+規範實現的一種。

術語

  • promise: 帶有按規範實現的then方法的對象或函數
  • thenable: 定義了then方法的對象或函數
  • value: 任何合法的JavaScript值(包括undefinedthenable或是promise),終值
  • exception: 使用throw拋出的值
  • reason: 指示promise被拒緣由的值,拒因

規範要求

(一). Promise的狀態

一個`promise`必須是這三種狀態之一:

- `pending`(進行中、等待中)
- `fulfilled`(被完成、被執行)
- `rejected`(被拒絕)

狀態的遷移:

1. 當`pending`時,`promise`的狀態能夠變到`fulfilled`或是`rejected`
2. 當`fulfilled`時,`promise`的狀態不可再變,同時必須持有一個**不可變**的`value`(終值)
3. 當`rejected`時,`promise`的狀態不可再變,同時必須持有一個**不可變**的`reason`(拒因)

這裏的**不可變**指的是恆等(便可用 `===` 判斷相等),但不意味着更深層次的不可變(如非基本類型值時,只要求引用地址相等)。
複製代碼

(二). then方法

一個`promise`必須提供一個`then`方法以訪問其當前值、終值和據因。方法接受兩個參數:
```js
promise.then(onFulfilled, onRejected)
```
1. `onFulfilled`、`onRejected`均爲可選參數,若是不是函數類型,則必須被忽略
2. 若是`onFulfilled`是個函數:在promise被完成前不可被調用;在promise被完成後必須被調用,以promise的終值做爲第一個參數;不能被屢次調用。
3. 若是`onRejected`是個函數:在promise被拒絕前不可被調用;在promise被拒絕後必須被調用,以promise的據因做爲第一個參數;不能被屢次調用。
4. 調用時機

    保證`onFulfilled`、`onRejected`在`then`被調用的那輪事件循環以後的新執行棧中異步執行。

    這一點可使用宏任務(macro-task)機制如`setTimeout`、`setImmediate`,或微任務(micro-task)機制如`MutationObserver`、`process.nextTick`來實現。
5. 調用要求

    `onFulfilled`和`onRejected`必須被做爲函數調用(即沒有 this 值)(在嚴格模式中,函數`this`的值爲`undefined`;在非嚴格模式中其爲全局對象)
6. `then`方法能夠被同一個promise調用屢次

    - 當promise被完成時,全部`onFulfilled`按註冊順序依次回調
    - 當promise被拒絕時,全部`onRejected`按註冊順序依次回調

7. `then`方法必須返回一個promise對象

    ```js
    promise2 = promise1.then(onFulfilled, onRejected);
    ```
    - 若是`onFulfilled`、`onRejected`返回一個值x,則運行下面決議`promise`的過程:`[[Resolve]](promise2, x)`
    - 若是`onFulfilled`、`onRejected`拋出一個異常e,則`promise2`必須拒絕執行,並返回拒因e
    - 若是`onFulfilled`不是函數且`promise1`成功執行, `promise2`必須成功執行並返回相同的值
    - 若是`onRejected`不是函數且`promise1`拒絕執行,`promise2`必須拒絕執行並返回相同的拒因
複製代碼

(三). 決議promise的過程(即[[Resolve]](promise, x)的具體實現)

1. 若是`x`與`promise`相等,以`TypeError`爲拒因拒絕執行promise
2. 若是`x`爲`Promise`的實例,則使`promise`接受`x`的狀態
    - 若是`x`進行中,`promise`也需保持進行中的狀態直至`x`被完成或拒絕
    - 若是`x`被完成,用相同的值執行`promise`
    - 若是`x`被拒絕,用相同的據因拒絕`promise`
3. 若是`x`爲函數或者對象
    1. 把`x.then`賦值給`then`
    2. 若是取`x.then`的值時拋出錯誤`e` ,則以`e`爲據因拒絕`promise`
        1. 若是`then`是函數,將`x`做爲函數的做用域`this`調用之。傳遞兩個回調函數做爲參數,第一個參數叫作`resolvePromise`,第二個參數叫作`rejectPromise`:

            - 若是`resolvePromise`以值`y`爲參數被調用,則運行`[[Resolve]](promise, y)`
            - 若是`rejectPromise`以據因`r`爲參數被調用,則以據因`r`拒絕`promise`
            - 若是`resolvePromise`和`rejectPromise`均被調用,或者被同一參數調用了屢次,則優先採用首次調用並忽略剩下的調用
            - 若是調用`then`方法拋出了異常`e`:
                - 若是`resolvePromise`或`rejectPromise`已經被調用,則忽略之
                - 不然以`e`爲據因拒絕`promise`
        2. 若是`then`不是函數,以`x`爲參數執行`promise`
    3. 若是`x`不爲對象或者函數,以`x`爲參數執行`promise`
複製代碼

測試

若是按照規範自行實現Promise,可使用下面官方提供的工具監測是否符合規範。

練習題

掘金上能夠找到一些不錯的練習題,用來鞏固知識再好不過了。

參考

相關文章
相關標籤/搜索