精讀Javascript系列(10) Promise——Promise/A+規範解讀Promise

前言:必要前提

閱讀本文有一個很重要的前提—— 就是知道 Promise是怎麼構造的,也可以說知道在構造Promise實例過程中究竟發生了什麼,是理解Promise必不可缺的關鍵一步。

試着問下自己這幾個問題:

  • Promise的接收的回調函數是怎麼執行的?(MDN稱之爲executor
  • resolvereject是什麼? 是誰提供的?
  • 返回的Promise是怎麼確定狀態的?

如果回答這些問題沒有把握,可以參考:

  • MDN Promise
  • Promise簡介
  • 其他博文:儘可能是 github上的,分析得頭頭是道、有理有據,但有些難理解就是了。

如果上面的問題都能很好的回答上來,那麼就正式開始用Promise/A+規範解讀Promise(雖說如此,還是要結合MDNtutorialpoint上的內容)。

開始吧。


預備:Promise基本構造(非規範)

注意

  • 這不是Promise/A+的一部分,但是知道它對於理解Promise規範的細節很有幫助。
  • 下面的內容我會在括號中引用,或是在僞代碼中引用。

任何一個Promise都會有兩個最關鍵的部分:

  • [[PromiseStatus]] : 表明操作執行後的狀態。默認爲pending ; 在Firefox瀏覽器中,該字段爲state
  • [[PromiseValue]] :是保存在Promise中的結果,它可能是一個reason(拒絕原因,異常.etc); 或是一個value(成功操作後提供的結果)。

關於[[PromiseStatus]][[PromiseValue]]的設置:

  1. 在調用resolve函數時,如果提供了參數y; 那麼[[PromiseValue]]則被設爲y的值。此時[[PromiseStatus]]將切換爲fulfilled (或resolved) ; 例如:resolve(y)
  2. 在調用reject函數時,如果提供了異常或一個值r, 那麼[[PromiseValue]]將被設爲r。它可能是一個異常(Exception)。此時[[PromiseStatus]]將切換爲rejected。 例如:reject(r)throw r

一旦[[PromiseStatus]]被設爲fulfilledrejected二者任何一個狀態,都可以說該Promise被settled

總結如下:
任何一個Promise實例prvaluereason都能表示爲:

pr . [[PromiseValue]] // 它可能是value ,也可能是 reason。

任何一個Promise實例pr的狀態都能表示爲:

pr . [[PromiseStatus]] // pending / fulfilled / rejected 三者之一。


Promise 與 then方法

注意:

  • 參考來源: Promise/A+規範
  • 雖然是參考規範,但是並非是規範的翻譯,小生也沒有翻譯規範的能力;下文大多是閱讀規範後的理解。

Promise/A+規範規定,Promise必須爲以下三種狀態之一:

  1. pending : 操作還未確定結果。此時promise狀態(即[[PromiseStatus]])可以切換到fulfilledrejected
  2. fulfilled:操作已經成功完成。無法再切換到其他狀態。此時必須有一個確定的value即[[PromiseValue]]無法再更改
  3. rejected:操作已經失敗。無法再切換到其他狀態。此時必須有一個確定的reason即[[PromiseValue]]無法再更改

任何一個promise實例都必須有一個then方法;同樣的,任何具有then方法的對象都可以稱之爲thenable對象。
如下所示:

promise.then(onFulfilled, onRejected)

  1. onFulfilledonRejected都是可選的,但是它們如果不是函數,都會被忽略。注意它們只能在promise被settled後調用並且只能被調用一次
  2. onFulfilled是一個函數時,promise的狀態爲fulfilled時被調用。並將promisevalue(即[[PromiseValue]])作爲onFulfilled的第一個參數。
  3. onRejected是一個函數時,promise的狀態爲 rejected 狀態時被調用,並且 promisereason(也是[[PromiseValue]]) 作爲它的第一個參數。

注意

  • 只有在當前任務(即宏任務)完成後才能調用onFulfilledonRejected回調;如果當前宏任務未執行完畢,那麼它們都不能被調用。 這保證了then異步的。
  • 一個then方法可以被同一個promise調用多次。

寫一個僞代碼:

 promise.then((value)=>{    // onFulfilled callback
     // [[PromiseStatus]] === fulfilled
     // [[PromiseValue]] === value 
     
 }, (reason)=>{          // onRejected callback
         // [[PromiseStatus]] === rejected
          // [[PromiseValue]] === reason
 })

then方法必須返回一個Promise實例
在知道怎麼將返回值轉換爲promise前,必須知道[[Resolve]](有大佬譯作promise解決程序,我就借用了)


Promise解決程序[[Resolve]]

[[Resolve]]是一個抽象操作,即:將x轉換爲Promise實例promise1的過程記作[[Resolve]](promise1,x)。這裏x可以是任何類型的值,如undefinedprimitive data(原始值)、thenable對象等等。

僞代碼如下:

x , promise1 // x 是任何值。

[[Resolve]](promise1, x)  // 執行Promise解決程序, x 和 promise作爲參數

promise1 // 是根據值x轉換得到的Promise實例值。

注意

  • 再次重申, thenable是擁有then方法的對象。
  • 爲了防止混淆, 我會將Promise實例稱作promise1,而不是規範中的promise
  • 過程我會排除掉一些少見情形,例如xpromise1是相同時,會拋出TypeError錯誤等等。必要時我會帶上。
[[Resolve ]](promise1,x) 過程如下:

PA

  • 假定: x是一個Promise實例,那麼:
  1. 如果x處於pending狀態,那麼promise1狀態也將爲pending; 直至x切換到fulfilledrejected狀態爲止。
  2. 如果x處於fulfilled狀態,promise1也將切換到fulfilled狀態,並將promise1的value(即promise1.[[PromiseValue]],下同)也設爲x的value
  3. 如果x處於rejected狀態,promise1也將切換到rejected狀態,並將promise1的reason(也是promise1.[[PromiseValue]],下同)也設爲x的reason

PB :

  • 假定: x不可能是Promise實例;
  • 假定: x是一個對象函數
  • 假設 thenx.then ;即then = x.then
  1. 若取屬性x.then拋出異常er, 那麼將promise1的reason設爲er,並將promise1的狀態(即promise1 . [[PromiseStatus]],下同)設爲rejected
  2. 如果then 是一個函數,那麼進入PC; 此時xthenable對象
  3. 如果then 不是一個函數 (注意:x也是一個對象或函數), 此時x不是thenable對象; 那麼將promise1的狀態設爲fulfilled,並將promise1的value設爲值x

PC

  • 此時x必須是一個thenable對象
  • 假定thenx.then
  • 假定 x對象定義如下:
  x = {
      ....
      then(resolvePromise, rejectPromise){  // resolvePromise, rejectPromise 是回調函數 
         ... 
         ...
      }
  }
  1. then方法的this指向x,並調用then方法; 注意then方法接收兩個回調函數
  2. 如果在調用resolvePromise時提供了一個參數y, 即resolvePromise(y);那麼執行[[Resolve]](promise1, y) ; 這裏是一個遞歸
  3. 如果在調用rejectPromise時提供了一個參數r, 即rejectPromise(y) ; 那麼將promise1的reason設爲r;並且promise1的狀態也會切換到rejected
  4. 如果resolvePromiserejectPromise都被數次調用,那麼優先第一個調用,其餘調用都將被忽略
  5. 如果then方法內部拋出一個異常e
    1. 如果resolvePromiserejectPromise已經被調用,那麼忽略它。
    2. 否則,將promise1的reason設爲e;並且將並且promise1的狀態切換到rejected

PD

  • x不是Promise實例
  • x不是對象函數
  1. promise1的狀態設爲fulfilled,並且將promise1的value設爲x
then返回值解析:

例如:

promise2 = promise1.then(onFulfilled, onRejected);

  1. onFulfilledonRejected 返回一個值x。 執行[[Resolve]](promise2, x)
  2. onFulfilledonRejected 如果拋出一個異常e。那麼promise2狀態切換爲rejected,其reason設爲e
  3. onFulfilledonRejected 都不是函數時,則:
    1. 如果promise1處於fulfilled狀態,那麼promise2也必須切換到fulfilled狀態,並且將promise2的value設爲promise1的value
    2. 如果promise1處於rejected狀態,那麼promise2也必須切換到rejected狀態,並且將promise2的reason設爲promise1的reason

okay.


驗證

情形1: x 爲 Promise實例
 var pr0 = Promise.reject('wrong,ha,ha,..')
 var pr1 = pr0.then(undefined, r=>{
     return Promise.reject('Madadesu')
 })
 console.log(pr1);  // pending
 setTimeout(console.log, 0,pr1)
 pr1.catch(()=>{});

輸出:
在這裏插入圖片描述參考 **規則 PA.3 **

情形2:x 是非thenable 的對象
  var pr0 = Promise.resolve({name:'Tadashi',age:30})
  console.log(pr0);

輸出:
在這裏插入圖片描述
參考 規則PB.3

情形3:x 是一個 thenable對象
   var x = {
      name:'tadane',
      age:30,
      then(res,rej){
          res(this.name+' '+this.age)
          console.log(11111);	// 輸出
          
          rej('tareru out!!')
          console.log(22222);   // 輸出
          
          throw new Error('....')
          console.log(3333);        // 未執行
          
      }
  }
  var pr0 = Promise.resolve(x)
  setTimeout(console.log, 0, pr0)

輸出:
在這裏插入圖片描述
參考規則PC.2

情形4:拋出異常
  var x = {
      name:'tadane',
      age:30,
      then(res,rej){
          throw new Error('bad js')
      }
  }
  var pr0 = Promise.resolve(x)
  setTimeout(console.log, 0, pr0)
  pr0.catch((e)=>{console.log(e.message);  })

仍然是 PC。其次,即使是在其他情形中,一旦拋出異常,promise也總是被設爲rejected
在這裏插入圖片描述

情形5: x爲原始值或undefined
  var p0 = Promise.reject('bad js')
  var p1 = p0.then(null, r=>{
      return 1111
  })
  setTimeout(console.log,0,p1)

輸出
在這裏插入圖片描述
參考規則PD

常見情形就這麼幾個例子吧。
如果有其他的,可以自行驗證

畫個圖

哎,我也搞個圖。
在這裏插入圖片描述

最後

原本想帶上catchfinally什麼的;但發現算了。概念又都堆到一起去了。而且這些本來就沒什麼難度,所以就跳過吧。

下一篇,想搞一個async functionasync generator,這樣銜接應該沒什麼問題吧……,入門時候都應該學過……吧。。javascript裏面我感覺最有意思的部分就是生成器這裏了。這裏真的向其他地方借鑑了不少,例如RubyC#什麼的,搞得我險些以爲學的不是Javascript。

本文有點標題黨,雖然是參考Promise/A+規範了,但也大改特改了一番,難免有失誤,還請各位多多指正……

同步轉發:
https://juejin.im/post/5eda1fa2f265da770c0efb80

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章