前言:必要前提
閱讀本文有一個很重要的前提—— 就是知道 Promise
是怎麼構造的,也可以說知道在構造Promise實例過程中究竟發生了什麼,是理解Promise必不可缺的關鍵一步。
試着問下自己這幾個問題:
- Promise的接收的回調函數是怎麼執行的?(MDN稱之爲
executor
) resolve
和reject
是什麼? 是誰提供的?- 返回的
Promise
是怎麼確定狀態的?
如果回答這些問題沒有把握,可以參考:
- MDN Promise
- Promise簡介
- 其他博文:儘可能是 github上的,分析得頭頭是道、有理有據,但有些難理解就是了。
如果上面的問題都能很好的回答上來,那麼就正式開始用Promise/A+
規範解讀Promise
(雖說如此,還是要結合MDN 和 tutorialpoint上的內容)。
開始吧。
預備:Promise基本構造(非規範)
注意:
- 這不是
Promise/A+
的一部分,但是知道它對於理解Promise
規範的細節很有幫助。 - 下面的內容我會在括號中引用,或是在僞代碼中引用。
任何一個Promise
都會有兩個最關鍵的部分:
[[PromiseStatus]]
: 表明操作執行後的狀態。默認爲pending
; 在Firefox
瀏覽器中,該字段爲state
[[PromiseValue]]
:是保存在Promise
中的結果,它可能是一個reason
(拒絕原因,異常.etc); 或是一個value
(成功操作後提供的結果)。
關於[[PromiseStatus]]
和[[PromiseValue]]
的設置:
- 在調用
resolve
函數時,如果提供了參數y
; 那麼[[PromiseValue]]
則被設爲y
的值。此時[[PromiseStatus]]
將切換爲fulfilled
(或resolved
) ; 例如:resolve(y)
- 在調用
reject
函數時,如果提供了異常或一個值r
, 那麼[[PromiseValue]]
將被設爲r
。它可能是一個異常(Exception)。此時[[PromiseStatus]]
將切換爲rejected
。 例如:reject(r)
或throw r
一旦[[PromiseStatus]]
被設爲fulfilled
或rejected
二者任何一個狀態,都可以說該Promise被settled。
總結如下:
任何一個Promise
實例pr
的value
或reason
都能表示爲:
pr . [[PromiseValue]] // 它可能是value ,也可能是 reason。
任何一個Promise
實例pr
的狀態都能表示爲:
pr . [[PromiseStatus]] // pending / fulfilled / rejected 三者之一。
Promise 與 then方法
注意:
- 參考來源: Promise/A+規範
- 雖然是參考規範,但是並非是規範的翻譯,小生也沒有翻譯規範的能力;下文大多是閱讀規範後的理解。
Promise/A+
規範規定,Promise
必須爲以下三種狀態之一:
pending
: 操作還未確定結果。此時promise
狀態(即[[PromiseStatus]]
)可以切換到fulfilled
或rejected
fulfilled
:操作已經成功完成。無法再切換到其他狀態。此時必須有一個確定的value
(即[[PromiseValue]]無法再更改)rejected
:操作已經失敗。無法再切換到其他狀態。此時必須有一個確定的reason
(即[[PromiseValue]]無法再更改)
任何一個promise
實例都必須有一個then
方法;同樣的,任何具有then
方法的對象都可以稱之爲thenable
對象。
如下所示:
promise.then(onFulfilled, onRejected)
onFulfilled
與onRejected
都是可選的,但是它們如果不是函數,都會被忽略。注意它們只能在promise被settled後調用並且只能被調用一次。onFulfilled
是一個函數時,promise
的狀態爲fulfilled
時被調用。並將promise
的value
(即[[PromiseValue]]
)作爲onFulfilled
的第一個參數。onRejected
是一個函數時,promise
的狀態爲rejected
狀態時被調用,並且promise
的reason
(也是[[PromiseValue]]
) 作爲它的第一個參數。
注意:
- 只有在當前任務(即宏任務)完成後才能調用
onFulfilled
與onRejected
回調;如果當前宏任務未執行完畢,那麼它們都不能被調用。 這保證了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
可以是任何類型的值,如undefined
、primitive data
(原始值)、thenable
對象等等。
僞代碼如下:
x , promise1 // x 是任何值。
[[Resolve]](promise1, x) // 執行Promise解決程序, x 和 promise作爲參數
promise1 // 是根據值x轉換得到的Promise實例值。
注意:
- 再次重申, thenable是擁有
then
方法的對象。 - 爲了防止混淆, 我會將
Promise
實例稱作promise1
,而不是規範中的promise
。 - 過程我會排除掉一些少見情形,例如
x
和promise1
是相同時,會拋出TypeError
錯誤等等。必要時我會帶上。
[[Resolve ]](promise1,x) 過程如下:
PA:
- 假定:
x
是一個Promise
實例,那麼:
- 如果
x
處於pending
狀態,那麼promise1
狀態也將爲pending
; 直至x
切換到fulfilled
或rejected
狀態爲止。 - 如果
x
處於fulfilled
狀態,promise1
也將切換到fulfilled
狀態,並將promise1的value
(即promise1.[[PromiseValue]]
,下同)也設爲x的value
- 如果
x
處於rejected
狀態,promise1
也將切換到rejected
狀態,並將promise1的reason
(也是promise1.[[PromiseValue]]
,下同)也設爲x的reason
PB :
- 假定:
x
不可能是Promise
實例; - 假定:
x
是一個對象或函數。 - 假設
then
爲x.then
;即then = x.then
;
- 若取屬性
x.then
拋出異常er
, 那麼將promise1的reason
設爲er
,並將promise1
的狀態(即promise1 . [[PromiseStatus]]
,下同)設爲rejected
- 如果
then
是一個函數,那麼進入PC; 此時x
爲thenable
對象 - 如果
then
不是一個函數 (注意:x
也是一個對象或函數), 此時x
不是thenable
對象; 那麼將promise1
的狀態設爲fulfilled
,並將promise1的value
設爲值x
。
PC:
- 此時
x
必須是一個thenable
對象 - 假定
then
是x.then
- 假定
x
對象定義如下:
x = {
....
then(resolvePromise, rejectPromise){ // resolvePromise, rejectPromise 是回調函數
...
...
}
}
- 將
then
方法的this
指向x
,並調用then
方法; 注意then
方法接收兩個回調函數。 - 如果在調用
resolvePromise
時提供了一個參數y
, 即resolvePromise(y)
;那麼執行[[Resolve]](promise1, y)
; 這裏是一個遞歸。 - 如果在調用
rejectPromise
時提供了一個參數r
, 即rejectPromise(y)
; 那麼將promise1的reason
設爲r
;並且promise1
的狀態也會切換到rejected
。 - 如果
resolvePromise
和rejectPromise
都被數次調用,那麼優先第一個調用,其餘調用都將被忽略。 - 如果
then
方法內部拋出一個異常e
:- 如果
resolvePromise
和rejectPromise
已經被調用,那麼忽略它。 - 否則,將
promise1的reason
設爲e
;並且將並且promise1
的狀態切換到rejected
- 如果
PD:
x
不是Promise
實例x
不是對象或函數。
- 將
promise1
的狀態設爲fulfilled
,並且將promise1的value
設爲x
。
then返回值解析:
例如:
promise2 = promise1.then(onFulfilled, onRejected);
onFulfilled
或onRejected
返回一個值x
。 執行[[Resolve]](promise2, x)
onFulfilled
或onRejected
如果拋出一個異常e
。那麼promise2
狀態切換爲rejected
,其reason
設爲e
- 當
onFulfilled
或onRejected
都不是函數時,則:- 如果
promise1
處於fulfilled
狀態,那麼promise2
也必須切換到fulfilled
狀態,並且將promise2的value
設爲promise1的value
- 如果
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
常見情形就這麼幾個例子吧。
如果有其他的,可以自行驗證
畫個圖
哎,我也搞個圖。
最後
原本想帶上catch
、 finally
什麼的;但發現算了。概念又都堆到一起去了。而且這些本來就沒什麼難度,所以就跳過吧。
下一篇,想搞一個async function
和 async generator
,這樣銜接應該沒什麼問題吧……,入門時候都應該學過……吧。。javascript裏面我感覺最有意思的部分就是生成器這裏了。這裏真的向其他地方借鑑了不少,例如Ruby
和C#
什麼的,搞得我險些以爲學的不是Javascript。
本文有點標題黨,雖然是參考Promise/A+
規範了,但也大改特改了一番,難免有失誤,還請各位多多指正……
同步轉發:
https://juejin.im/post/5eda1fa2f265da770c0efb80