大白話講解Promise(二)理解Promise規範

http://www.cnblogs.com/lvdabao/p/5320705.html @呂大豹 html

上一篇咱們講解了ES6中Promise的用法,可是知道了用法還遠遠不夠,做爲一名專業的前端工程師,還必須通曉原理。因此,爲了補全咱們關於Promise的知識樹,有必要理解Promise/A+規範,理解了它你才能知道Promise內部是怎麼回事,咱們ES6中的Promise是如何一路走來的。
 
網上關於Promise/A+的翻譯文檔不少,因此我就不翻譯一次了,本篇的目的在於爲文檔增長一些標註,以幫助咱們更好的理解。翻譯內容引用自: http://malcolmyu.github.io/malnote/2015/06/12/Promises-A-Plus/,部分我認爲不太合適的有做修改。
 

術語


 

Promise

promise 是一個擁有 then 方法的對象或函數,其行爲符合本規範;前端

thenable

是一個定義了 then 方法的對象或函數,文中譯做「擁有 then 方法」;jquery

值(value)

指任何 JavaScript 的合法值(包括 undefined , thenable 和 promise);git

異常(exception)

是使用 throw 語句拋出的一個值。github

拒絕緣由(reason)

表示一個 promise 的拒絕緣由。面試

要求


 
Promise 的狀態
一個 Promise 的當前狀態必須爲如下三種狀態中的一種: 等待態(Pending)完成 態(Fulfilled)和完成 態(Rejected)

等待態(Pending)

處於等待態時,promise 需知足如下條件:算法

  • 能夠遷移至完成態或拒絕態
 
完成態(Fulfilled)
處於完成態時,promise 需知足如下條件:
  • 不能遷移至其餘任何狀態
  • 必須擁有一個不可變的終值

拒絕態(Rejected)

處於拒絕態時,promise 需知足如下條件:promise

  • 不能遷移至其餘任何狀態
  • 必須擁有一個不可變的據因

這裏的不可變指的是恆等(便可用 === 判斷相等),而不是意味着更深層次的不可變(譯者注: 蓋指當 value 或 reason 不是基本值時,只要求其引用地址相等,但屬性值可被更改)。瀏覽器

Then 方法

一個 promise 必須提供一個 then 方法以訪問其當前值、終值和據因。前端工程師

promise 的  then 方法接受兩個參數:
 
promise.then(onFulfilled, onRejected)
參數可選

onFulfilled 和 onRejected 都是可選參數。

  • 若是 onFulfilled 不是函數,其必須被忽略
  • 若是 onRejected 不是函數,其必須被忽略
注:若是咱們只想傳onRejected而不想傳onFulfilled,能夠這麼寫:pormise.then(null, onRejected)

onFulfilled 特性

若是 onFulfilled 是函數:

  • 當 promise 執行結束後其必須被調用,其第一個參數爲 promise 的終值
  • 在 promise 執行結束前其不可被調用
  • 其調用次數不可超過一次

onRejected 特性

若是 onRejected 是函數:

  • 當 promise 被拒絕執行後其必須被調用,其第一個參數爲 promise 的據因
  • 在 promise 被拒絕執行前其不可被調用
  • 其調用次數不可超過一次

調用時機

onFulfilled 和 onRejected 只有在執行環境堆棧僅包含平臺代碼時纔可被調用 注1

調用要求

onFulfilled 和  onRejected 必須被做爲函數調用(即沒有  this 值) 注2
 
注:也就是說,咱們在promise中就別用this了。

屢次調用

then 方法能夠被同一個 promise 調用屢次

  • 當 promise 成功執行時,全部 onFulfilled 需按照其註冊順序依次回調
  • 當 promise 被拒絕執行時,全部的 onRejected 需按照其註冊順序依次回調
注:這裏解釋了咱們能夠鏈式調用,promise.then().then().then()

返回

then 方法必須返回一個  promise 對象  注3
promise2 = promise1.then(onFulfilled, onRejected);
 
注:這就是咱們可以進行鏈式調用的緣由,由於then方法返回的仍是一個promise對象。
 
若是  onFulfilled 或者  onRejected 返回一個值  x ,則運行下面的  Promise 解決過程[[Resolve]](promise2, x)
  • 若是 onFulfilled 或者 onRejected 拋出一個異常 e ,則 promise2 必須拒絕執行,並返回拒因 e
  • 若是 onFulfilled 不是函數且 promise1 成功執行, promise2 必須成功執行並返回相同的值
  • 若是 onRejected 不是函數且 promise1 拒絕執行, promise2 必須拒絕執行並返回相同的據因

Promise 解決過程

Promise 解決過程 是一個抽象的操做,其需輸入一個 promise 和一個值,咱們表示爲 [[Resolve]](promise, x),若是 x 有then 方法且看上去像一個 Promise ,解決程序即嘗試使 promise 接受 x 的狀態;不然其用 x 的值來執行 promise 。

這種 thenable 的特性使得 Promise 的實現更具備通用性:只要其暴露出一個遵循 Promise/A+ 協議的 then 方法便可;這同時也使遵循 Promise/A+ 規範的實現能夠與那些不太規範但可用的實現能良好共存。

運行 [[Resolve]](promise, x) 需遵循如下步驟:

x 與 promise 相等

若是 promise 和 x 指向同一對象,以 TypeError 爲據因拒絕執行 promise

x 爲 Promise

若是 x 爲 Promise ,則使 promise 接受 x 的狀態 注4

  • 若是 x 處於等待態, promise 需保持爲等待態直至 x 被執行或拒絕
  • 若是 x 處於完成態,用相同的值執行 promise
  • 若是 x 處於拒絕態,用相同的據因拒絕 promise
注:這裏就是解釋咱們鏈式調用then時,能夠繼續進行異步操做,只要在onFulfilled中繼續返回一個promise對象便可。例如:
runAsync1()
.then(function(data){
    console.log(data);
    return runAsync2(); //返回值爲promise對象
})
.then(function(data){
    console.log(data);
    return runAsync3();
})

x 爲對象或函數

若是 x 爲對象或者函數:

  • 把 x.then 賦值給 then 注5
  • 若是取 x.then 的值時拋出錯誤 e ,則以 e 爲據因拒絕 promise
  • 若是 then 是函數,將 x 做爲函數的做用域 this 調用之。傳遞兩個回調函數做爲參數,第一個參數叫作 resolvePromise ,第二個參數叫作 rejectPromise:
    • 若是 resolvePromise 以值 y 爲參數被調用,則運行 [[Resolve]](promise, y)
    • 若是 rejectPromise 以據因 r 爲參數被調用,則以據因 r 拒絕 promise
    • 若是 resolvePromise 和 rejectPromise 均被調用,或者被同一參數調用了屢次,則優先採用首次調用並忽略剩下的調用
    • 若是調用 then 方法拋出了異常 e
      • 若是 resolvePromise 或 rejectPromise 已經被調用,則忽略之
      • 不然以 e 爲據因拒絕 promise
    • 若是 then 不是函數,以 x 爲參數執行 promise
  • 若是 x 不爲對象或者函數,以 x 爲參數執行 promise

若是一個 promise 被一個循環的 thenable 鏈中的對象解決,而 [[Resolve]](promise, thenable) 的遞歸性質又使得其被再次調用,根據上述的算法將會陷入無限遞歸之中。算法雖不強制要求,但也鼓勵施者檢測這樣的遞歸是否存在,若檢測到存在則以一個可識別的TypeError 爲據因來拒絕 promise 注6

註釋


  • 注1 這裏的平臺代碼指的是引擎、環境以及 promise 的實施代碼。實踐中要確保 onFulfilled 和 onRejected 方法異步執行,且應該在 then 方法被調用的那一輪事件循環以後的新執行棧中執行。這個事件隊列能夠採用「宏任務(macro-task)」機制或者「微任務(micro-task)」機制來實現。因爲 promise 的實施代碼自己就是平臺代碼(譯者注: 即都是 JavaScript),故代碼自身在處理在處理程序時可能已經包含一個任務調度隊列或『跳板』)。

    譯者注: 這裏說起了 macrotask 和 microtask 兩個概念,這表示異步任務的兩種分類。在掛起任務時,JS 引擎會將全部任務按照類別分到這兩個隊列中,首先在 macrotask 的隊列(這個隊列也被叫作 task queue)中取出第一個任務,執行完畢後取出 microtask 隊列中的全部任務順序執行;以後再取 macrotask 任務,周而復始,直至兩個隊列的任務都取完。

    兩個類別的具體分類以下:

    • macro-task: script(總體代碼), setTimeoutsetIntervalsetImmediate, I/O, UI rendering
    • micro-task: process.nextTickPromises(這裏指瀏覽器實現的原生 Promise), Object.observe,MutationObserver

      詳見 stackoverflow 解答 或 這篇博客

  • 注2 也就是說在 嚴格模式(strict) 中,函數 this 的值爲 undefined ;在非嚴格模式中其爲全局對象。

  • 注3 代碼實如今知足全部要求的狀況下能夠容許 promise2 === promise1 。每一個實現都要文檔說明其是否容許以及在何種條件下容許 promise2 === promise1 。

  • 注4 整體來講,若是 x 符合當前實現,咱們才認爲它是真正的 promise 。這一規則容許那些特例實現接受符合已知要求的 Promises 狀態。

  • 注5 這步咱們先是存儲了一個指向 x.then 的引用,而後測試並調用該引用,以免屢次訪問 x.then 屬性。這種預防措施確保了該屬性的一致性,由於其值可能在檢索調用時被改變。

  • 注6 實現不該該對  thenable 鏈的深度設限,並假定超出本限制的遞歸就是無限循環。只有真正的循環遞歸才應能致使  TypeError 異常;若是一條無限長的鏈上  thenable 均不相同,那麼遞歸下去永遠是正確的行爲。
 
補充:Promise/A+並未規定all和race方法,也就是說這兩個方法是ES6本身增長的了。由於Promise/A+只是規範,ES6是作了本身的實現,固然能夠本身加了。實現Promise規範的庫有不少,好比jquery、dojo等,jquery在實現的時候還增長了更多的方法,咱們在下一篇會作講解。網上也有很多朋友本身實現過Promise/A+,列出來供你們參考:
 
對於規範,有些同窗不太想看,我平時在面試的時候問起一些規範相關的問題,大多數面試者都回答不來。有些人或許會說,做爲司機會開車不就好了,難道要知道汽車是怎麼造的嗎?那我這裏想反問一下,你準備當一生司機嗎?對於規範能夠不那麼充分研究,可是起碼得知道關鍵部分,有這樣一個意識,對於之後本身成長爲大牛也有所幫助。
相關文章
相關標籤/搜索