感謝社區(qū)中各位的大力支持,譯者再次奉上一點(diǎn)點(diǎn)福利:阿里云產(chǎn)品券,享受所有官網(wǎng)優(yōu)惠,并抽取幸運(yùn)大獎(jiǎng):點(diǎn)擊這里領(lǐng)取
在第二章中,我們定位了在使用回調(diào)表達(dá)程序異步性和管理并發(fā)的兩個(gè)主要類別的不足:缺乏順序性和缺乏可靠性‰柿桑現(xiàn)在我們更親近地理解了問題,是時(shí)候?qū)⑽覀兊淖⒁饬D(zhuǎn)向解決它們的模式了公黑。
我們首先想要解決的是 控制倒轉(zhuǎn) 問題邑商,信任是如此脆弱而且是如此的容易丟失摄咆。
回想一下,我們將我們的程序的延續(xù)包裝進(jìn)一個(gè)回調(diào)函數(shù)中人断,將這個(gè)回調(diào)交給另一個(gè)團(tuán)體(甚至是潛在的外部代碼)吭从,并雙手合十祈禱它會(huì)做正確的事情并調(diào)用這個(gè)回調(diào)。
我們這么做是因?yàn)槲覀兿胝f恶迈,“這是 稍后 將要發(fā)生的事涩金,在當(dāng)前的步驟完成之后∠局伲”
但是如果我們能夠反向倒轉(zhuǎn)這種 控制倒轉(zhuǎn) 呢步做?如果不是將我們程序的延續(xù)交給另一個(gè)團(tuán)體,而是希望它返回給我們一個(gè)可以知道它何時(shí)完成的能力奈附,然后我們的代碼可以決定下一步做什么呢全度?
這種規(guī)范被稱為 Promise。
Promise正在像風(fēng)暴一樣席卷JS世界桅狠,因?yàn)殚_發(fā)者和語言規(guī)范作者之流拼命地想要在他們的代碼/設(shè)計(jì)中結(jié)束回調(diào)地獄的瘋狂讼载。事實(shí)上轿秧,大多數(shù)新被加入JS/DOM平臺(tái)的異步API都是建立在Promise之上的中跌。所以深入學(xué)習(xí)它們可能是個(gè)好主意,你不這么認(rèn)為嗎菇篡?
注意: “立即”這個(gè)詞將在本章頻繁使用漩符,一般來說它指代一些Promise解析行為。然而驱还,本質(zhì)上在所有情況下嗜暴,“立即”意味著就工作隊(duì)列行為(參見第一章)而言,不是嚴(yán)格同步的 現(xiàn)在 的感覺议蟆。
什么是Promise闷沥?
當(dāng)開發(fā)者們決定要學(xué)習(xí)一種新技術(shù)或模式的時(shí)候,他們的第一步總是“給我看代碼咐容!”舆逃。摸著石頭過河對(duì)我們來講是十分自然的。
但事實(shí)上僅僅考察API丟失了一些抽象過程戳粒。Promise是這樣一種工具:它能非常明顯地看出使用者是否理解了它是為什么和關(guān)于什么路狮,還是僅僅學(xué)習(xí)和使用API。
所以在我展示Promise的代碼之前蔚约,我想在概念上完整地解釋一下Promise到底是什么奄妨。我希望這能更好地指引你探索如何將Promise理論整合到你自己的異步流程中。
帶著這樣的想法苹祟,讓我們來看兩種類比砸抛,來解釋Promise是什么评雌。
未來的值
想象這樣的場(chǎng)景:我走到快餐店的柜臺(tái)前,點(diǎn)了一個(gè)起士漢堡锰悼。并交了1.47美元的現(xiàn)金柳骄。通過點(diǎn)餐和付款,我為得到一個(gè) 值(起士漢堡)制造了一個(gè)請(qǐng)求箕般。我發(fā)起了一個(gè)事務(wù)耐薯。
但是通常來說,起士漢堡不會(huì)立即到我手中丝里。收銀員交給一些東西代替我的起士漢堡:一個(gè)帶有點(diǎn)餐排隊(duì)號(hào)的收據(jù)曲初。這個(gè)點(diǎn)餐號(hào)是一個(gè)“我欠你”的許諾(Promise),它保證我最終會(huì)得到我的起士漢堡杯聚。
于是我就拿著我的收據(jù)和點(diǎn)餐號(hào)臼婆。我知道它代表我的 未來的起士漢堡,所以我無需再擔(dān)心它——除了挨餓幌绍!
在我等待的時(shí)候颁褂,我可以做其他的事情,比如給我的朋友發(fā)微信說傀广,“嘿颁独,一塊兒吃午餐嗎?我要吃起士漢堡”伪冰。
我已經(jīng)在用我的 未來的起士漢堡 進(jìn)行推理了誓酒,即便它還沒有到我手中。我的大腦可以這么做是因?yàn)樗鼘Ⅻc(diǎn)餐號(hào)作為起士漢堡的占位符號(hào)贮聂。這個(gè)占位符號(hào)實(shí)質(zhì)上使這個(gè)值 與時(shí)間無關(guān)靠柑。它是一個(gè) 未來的值。
最終吓懈,我聽到歼冰,“113號(hào)!”耻警。于是我愉快地拿著收據(jù)走回柜臺(tái)前隔嫡。我把收據(jù)遞給收銀員,拿回我的起士漢堡榕栏。
換句話說畔勤,一旦我的 未來的值 準(zhǔn)備好,我就用我的許諾值換回值本身扒磁。
但還有另外一種可能的輸出庆揪。它們叫我的號(hào),但當(dāng)我去取起士漢堡時(shí)妨托,收銀員遺憾地告訴我缸榛,“對(duì)不起吝羞,看起來我們的起士漢堡賣光了∧诳牛”把這種場(chǎng)景下顧客有多沮喪放在一邊钧排,我們可以看到 未來的值 的一個(gè)重要性質(zhì):它們既可以表示成功也可以表示失敗。
每次我點(diǎn)起士漢堡時(shí)均澳,我都知道我要么最終得到一個(gè)起士漢堡恨溜,要么得到起士漢堡賣光的壞消息,并且不得不考慮中午吃點(diǎn)兒別的東西找前。
注意: 在代碼中糟袁,事情沒有這么簡(jiǎn)單,因?yàn)檫€隱含著一種點(diǎn)餐號(hào)永遠(yuǎn)也不會(huì)被叫到的情況躺盛,這時(shí)我們就被擱置在了一種無限等待的未解析狀態(tài)项戴。我們待會(huì)兒再回頭處理這種情況。
現(xiàn)在和稍后的值
這一切也許聽起來在思維上太過抽象而不能實(shí)施在你的代碼中槽惫。那么周叮,讓我們更具體一些。
然而界斜,在我們能介紹Promise是如何以這種方式工作之前仿耽,我們先看看我們已經(jīng)明白的代碼——回調(diào)!——是如何處理這些 未來值 的锄蹂。
在你寫代碼來推導(dǎo)一個(gè)值時(shí)氓仲,比如在一個(gè)number
上進(jìn)行數(shù)學(xué)操作水慨,不論你是否理解得糜,對(duì)于這個(gè)值你已經(jīng)假設(shè)了某些非常基礎(chǔ)的事實(shí)——這個(gè)值已經(jīng)是一個(gè)實(shí)在的 現(xiàn)在 值:
var x, y = 2;
console.log( x + y ); // NaN <-- 因?yàn)閌x`還沒有被賦值
x + y
操作假定x
和y
都已經(jīng)被設(shè)定好了晰洒。用我們一會(huì)將要闡述的術(shù)語來講朝抖,我們假定x
和y
的值已經(jīng)被 解析(resovle) 了。
期盼+
操作符本身能夠魔法般地檢測(cè)并等待x
和y
的值被解析(也就是準(zhǔn)備好)谍珊,然后僅在那之后才進(jìn)行操作是沒道理的治宣。如果不同的語句 現(xiàn)在 完成而其他的 稍后 完成,這就會(huì)在程序中造成混亂砌滞,對(duì)吧侮邀?
如果兩個(gè)語句中的一個(gè)(或兩者同時(shí))可能還沒有完成,你如何才能推斷它們的關(guān)系呢贝润?如果語句2要依賴語句1的完成绊茧,那么這里僅有兩種輸出:不是語句1 現(xiàn)在 立即完成而且一切處理正常進(jìn)行,就是語句1還沒有完成打掘,所以語句2將會(huì)失敗华畏。
如果這些東西聽起來很像第一章的內(nèi)容鹏秋,很好!
回到我們的x + y
的數(shù)學(xué)操作亡笑。想象有一種方法可以說侣夷,“將x
和y
相加,但如果它們中任意一個(gè)還沒有被設(shè)置仑乌,就等到它們都被設(shè)置百拓。盡快將它們相加∥酰”
你的大腦也許剛剛跳進(jìn)回調(diào)耐版。好吧,那么...
function add(getX,getY,cb) {
var x, y;
getX( function(xVal){
x = xVal;
// 兩者都準(zhǔn)備好了压汪?
if (y != undefined) {
cb( x + y ); // 發(fā)送加法的結(jié)果
}
} );
getY( function(yVal){
y = yVal;
// 兩者都準(zhǔn)備好了粪牲?
if (x != undefined) {
cb( x + y ); // 發(fā)送加法的結(jié)果
}
} );
}
// `fetchX()`和`fetchY()`是同步或異步的函數(shù)
add( fetchX, fetchY, function(sum){
console.log( sum ); // 很簡(jiǎn)單吧?
} );
花點(diǎn)兒時(shí)間來感受一下這段代碼的美妙(或者丑陋)止剖,我耐心地等你腺阳。
雖然丑陋是無法否認(rèn)的,但是關(guān)于這種異步模式有一些非常重要的事情需要注意穿香。
在這段代碼中亭引,我們將x
和y
作為未來的值對(duì)待,我們將add(..)
操作表達(dá)為:(從外部看來)它并不關(guān)心x
或y
或它們兩者現(xiàn)在是否可用皮获。換句話所焙蚓,它泛化了 現(xiàn)在 和 稍后,如此我們可以信賴add(..)
操作的一個(gè)可預(yù)測(cè)的結(jié)果洒宝。
通過使用一個(gè)臨時(shí)一致的add(..)
——它跨越 現(xiàn)在 和 稍后 的行為是相同的——異步代碼的推理變得容易的多了购公。
更直白地說:為了一致地處理 現(xiàn)在 和 稍后,我們將它們都作為 稍后:所有的操作都變成異步的雁歌。
當(dāng)然,這種粗略的基于回調(diào)的方法留下了許多提升的空間靠瞎。為了理解在不用關(guān)心 未來的值 在時(shí)間上什么時(shí)候變得可用的情況下推理它而帶來的好處比庄,這僅僅是邁出的一小步。
Promise值
我們絕對(duì)會(huì)在本章的后面深入更多關(guān)于Promise的細(xì)節(jié)——所以如果這讓你犯糊涂乏盐,不要擔(dān)心——但讓我們先簡(jiǎn)單地看一下我們?nèi)绾瓮ㄟ^Promise
來表達(dá)x + y
的例子:
function add(xPromise,yPromise) {
// `Promise.all([ .. ])`接收一個(gè)Promise的數(shù)組佳窑,
// 并返回一個(gè)等待它們?nèi)客瓿傻男翽romise
return Promise.all( [xPromise, yPromise] )
// 當(dāng)這個(gè)Promise被解析后,我們拿起收到的`X`和`Y`的值父能,并把它們相加
.then( function(values){
// `values`是一個(gè)從先前被解析的Promise那里收到的消息數(shù)組
return values[0] + values[1];
} );
}
// `fetchX()`和`fetchY()`分別為它們的值返回一個(gè)Promise神凑,
// 這些值可能在 *現(xiàn)在* 或 *稍后* 準(zhǔn)備好
add( fetchX(), fetchY() )
// 為了將兩個(gè)數(shù)字相加,我們得到一個(gè)Promise法竞。
// 現(xiàn)在我們鏈?zhǔn)降卣{(diào)用`then(..)`來等待返回的Promise被解析
.then( function(sum){
console.log( sum ); // 這容易多了耙厚!
} );
在這個(gè)代碼段中有兩層Promise强挫。
fetchX()
和fetchY()
被直接調(diào)用,它們的返回值(promiseQ)被傳入add(..)
俯渤。這些promise表示的值將在 現(xiàn)在 或 稍后 準(zhǔn)備好,但是每個(gè)promise都將行為泛化為與時(shí)間無關(guān)型宝。我們以一種時(shí)間無關(guān)的方式來推理X
和Y
的值八匠。它們是 未來值。
第二層是由add(..)
創(chuàng)建(通過Promise.all([ .. ])
)并返回的promise趴酣,我們通過調(diào)用then(..)
來等待它梨树。當(dāng)add(..)
操作完成后,我們的sum
未來值 就準(zhǔn)備好并可以打印了岖寞。我們將等待X
和Y
的 未來值 的邏輯隱藏在add(..)
內(nèi)部抡四。
注意: 在add(..)
內(nèi)部。Promise.all([ .. ])
調(diào)用創(chuàng)建了一個(gè)promise(它在等待promiseX
和promiseY
被解析)仗谆。鏈?zhǔn)秸{(diào)用.then(..)
創(chuàng)建了另一個(gè)promise指巡,它的return values[0] + values[1]
這一行會(huì)被立即解析(使用加法的結(jié)果)。這樣隶垮,我們鏈接在add(..)
調(diào)用末尾的then(..)
調(diào)用——在代碼段最后——實(shí)際上是在第二個(gè)被返回的promise上進(jìn)行操作藻雪,而非被Promise.all([ .. ])
創(chuàng)建的第一個(gè)promise。另外狸吞,雖然我們沒有在這第二個(gè)then(..)
的末尾鏈接任何操作勉耀,它也已經(jīng)創(chuàng)建了另一個(gè)promise,我們可以選擇監(jiān)聽/使用它蹋偏。這類Promise鏈的細(xì)節(jié)將會(huì)在本章后面進(jìn)行講解便斥。
就像點(diǎn)一個(gè)起士漢堡,Promise的解析可能是一個(gè)拒絕(rejection)而非完成(fulfillment)暖侨。不同的是椭住,被完成的Promise的值總是程序化的崇渗,而一個(gè)拒絕值——通常被稱為“拒絕理由”——既可以被程序邏輯設(shè)置字逗,也可以被運(yùn)行時(shí)異常隱含地設(shè)置。
使用Promise宅广,then(..)
調(diào)用實(shí)際上可以接受兩個(gè)函數(shù)葫掉,第一個(gè)用作完成(正如剛才所示),而第二個(gè)用作拒絕:
add( fetchX(), fetchY() )
.then(
// 完成處理器
function(sum) {
console.log( sum );
},
// 拒絕處理器
function(err) {
console.error( err ); // 倒霉跟狱!
}
);
如果在取得X
或Y
時(shí)出現(xiàn)了錯(cuò)誤俭厚,或在加法操作時(shí)某些事情不知怎地失敗了,add(..)
返回的promise就被拒絕了驶臊,傳入then(..)
的第二個(gè)錯(cuò)誤處理回調(diào)函數(shù)會(huì)從promise那里收到拒絕的值挪挤。
因?yàn)镻romise包裝了時(shí)間相關(guān)的狀態(tài)——等待當(dāng)前值的完成或拒絕——從外部看來叼丑,Promise本身是時(shí)間無關(guān)的,如此Promise就可以用可預(yù)測(cè)的方式組合扛门,而不用關(guān)心時(shí)間或底層的結(jié)果鸠信。
另外,一旦Promise被解析论寨,它就永遠(yuǎn)保持那個(gè)狀態(tài)——它在那個(gè)時(shí)刻變成了一個(gè) 不可變的值——而且可以根據(jù)需要 被監(jiān)聽 任意多次星立。
注意: 因?yàn)镻romise一旦被解析就是外部不可變的,所以現(xiàn)在將這個(gè)值傳遞給任何其他團(tuán)體都是安全的葬凳,而且我們知道它不會(huì)被意外或惡意地被修改绰垂。這在許多團(tuán)體監(jiān)聽同一個(gè)Promise的解析時(shí)特別有用。一個(gè)團(tuán)體去影響另一個(gè)團(tuán)體對(duì)Promise解析的監(jiān)聽能力是不可能的火焰。不可變性聽起來是一個(gè)學(xué)院派話題劲装,但它實(shí)際上是Promise設(shè)計(jì)中最基礎(chǔ)且最重要的方面之一,因此不能將它隨意地跳過昌简。
這是用于理解Promise的最強(qiáng)大且最重要的概念之一酱畅。通過大量的工作,你可以僅僅使用丑陋的回調(diào)組合來創(chuàng)建相同的效果江场,但這真的不是一個(gè)高效的策略纺酸,特別是你不得不一遍一遍地重復(fù)它。
Promise是一種用來包裝與組合 未來值,并且可以很容易復(fù)用的機(jī)制雄嚣。
完成事件
正如我們剛才看到的坛善,一個(gè)獨(dú)立的Promise作為一個(gè) 未來值 動(dòng)作。但還有另外一種方式考慮Promise的解析:在一個(gè)異步任務(wù)的兩個(gè)或以上步驟中樊诺,作為一種流程控制機(jī)制——俗稱“這個(gè)然后那個(gè)”。
讓我們想象調(diào)用foo(..)
來執(zhí)行某個(gè)任務(wù)音同。我們對(duì)它的細(xì)節(jié)一無所知词爬,我們也不關(guān)心。它可能會(huì)立即完成任務(wù)权均,也可能會(huì)花一段時(shí)間完成顿膨。
我們僅僅想簡(jiǎn)單地知道foo(..)
什么時(shí)候完成,以便于我們可以移動(dòng)到下一個(gè)任務(wù)叽赊。換句話說恋沃,我們想要一種方法被告知foo(..)
的完成,以便于我們可以 繼續(xù)必指。
在典型的JavaScript風(fēng)格中囊咏,如果你需要監(jiān)聽一個(gè)通知,你很可能會(huì)想到事件(event)。那么我們可以將我們的通知需求重新表述為梅割,監(jiān)聽由foo(..)
發(fā)出的 完成(或 繼續(xù))事件霜第。
注意: 將它稱為一個(gè)“完成事件”還是一個(gè)“繼續(xù)事件”取決于你的角度。你是更關(guān)心foo(..)
發(fā)生的事情户辞,還是更關(guān)心foo(..)
完成 之后 發(fā)生的事情庶诡??jī)煞N角度都對(duì)而且都有用。事件通知告訴我們foo(..)
已經(jīng) 完成咆课,但是 繼續(xù) 到下一個(gè)步驟也沒問題末誓。的確,你為了事件通知調(diào)用而傳入的回調(diào)函數(shù)本身书蚪,在前面我們稱它為一個(gè) 延續(xù)喇澡。因?yàn)?完成事件 更加聚焦于foo(..)
,也就是我們當(dāng)前注意的東西殊校,所以在這篇文章的其余部分我們稍稍偏向于使用 完成事件晴玖。
使用回調(diào),“通知”就是被任務(wù)(foo(..)
)調(diào)用的我們的回調(diào)函數(shù)为流。但是使用Promise呕屎,我們將關(guān)系扭轉(zhuǎn)過來,我們希望能夠監(jiān)聽一個(gè)來自于foo(..)
的事件敬察,當(dāng)我們被通知時(shí)秀睛,做相應(yīng)的處理。
首先莲祸,考慮一些假想代碼:
foo(x) {
// 開始做一些可能會(huì)花一段時(shí)間的事情
}
foo( 42 )
on (foo "completion") {
// 現(xiàn)在我們可以做下一步了蹂安!
}
on (foo "error") {
// 噢,在`foo(..)`中有某些事情搞錯(cuò)了
}
我們調(diào)用foo(..)
然后我們?cè)O(shè)置兩個(gè)事件監(jiān)聽器锐帜,一個(gè)給"completion"
田盈,一個(gè)給"error"
——foo(..)
調(diào)用的兩種可能的最終結(jié)果。實(shí)質(zhì)上缴阎,foo(..)
甚至不知道調(diào)用它的代碼監(jiān)聽了這些事件允瞧,這構(gòu)成了一個(gè)非常美妙的 關(guān)注分離(separation of concerns)。
不幸的是蛮拔,這樣的代碼將需要JS環(huán)境不具備的一些“魔法”(而且顯得有些不切實(shí)際)述暂。這里是一種用JS表達(dá)它的更自然的方式:
function foo(x) {
// 開始做一些可能會(huì)花一段時(shí)間的事情
// 制造一個(gè)`listener`事件通知能力并返回
return listener;
}
var evt = foo( 42 );
evt.on( "completion", function(){
// 現(xiàn)在我們可以做下一步了!
} );
evt.on( "failure", function(err){
// 噢语泽,在`foo(..)`中有某些事情搞錯(cuò)了
} );
foo(..)
明確地創(chuàng)建并返回了一個(gè)事件監(jiān)聽能力贸典,調(diào)用方代碼接收并在它上面注冊(cè)了兩個(gè)事件監(jiān)聽器。
很明顯這反轉(zhuǎn)了一般的面向回調(diào)代碼踱卵,而且是有意為之。與將回調(diào)傳入foo(..)
相反,它返回一個(gè)我們稱之為evt
的事件能力惋砂,它接收回調(diào)妒挎。
但如果你回想第二章,回調(diào)本身代表著一種 控制反轉(zhuǎn)西饵。所以反轉(zhuǎn)回調(diào)模式實(shí)際上是 反轉(zhuǎn)的反轉(zhuǎn)酝掩,或者說是一個(gè) 控制非反轉(zhuǎn)——將控制權(quán)歸還給我們希望保持它的調(diào)用方代碼,
一個(gè)重要的好處是眷柔,代碼的多個(gè)分離部分都可以被賦予事件監(jiān)聽能力期虾,而且它們都可在foo(..)
完成時(shí)被獨(dú)立地通知,來執(zhí)行后續(xù)的步驟:
var evt = foo( 42 );
// 讓`bar(..)`監(jiān)聽`foo(..)`的完成
bar( evt );
// 同時(shí)驯嘱,讓`baz(..)`監(jiān)聽`foo(..)`的完成
baz( evt );
控制非反轉(zhuǎn) 導(dǎo)致了更好的 關(guān)注分離镶苞,也就是bar(..)
和baz(..)
不必卷入foo(..)
是如何被調(diào)用的問題。相似地鞠评,foo(..)
也不必知道或關(guān)心bar(..)
和baz(..)
的存在或它們是否在等待foo(..)
完成的通知茂蚓。
實(shí)質(zhì)上,這個(gè)evt
對(duì)象是一個(gè)中立的第三方團(tuán)體剃幌,在分離的關(guān)注點(diǎn)之間進(jìn)行交涉聋涨。
Promise“事件”
正如你可能已經(jīng)猜到的,evt
事件監(jiān)聽能力是一個(gè)Promise的類比负乡。
在一個(gè)基于Promise的方式中牍白,前面的代碼段將會(huì)使foo(..)
創(chuàng)建并返回一個(gè)Promise
實(shí)例,而且這個(gè)promise將會(huì)被傳入bar(..)
和baz(..)
抖棘。
注意: 我們監(jiān)聽的Promise解析“事件”并不是嚴(yán)格的事件(雖然它們?yōu)榱四承┠康谋憩F(xiàn)得像事件)淹朋,而且它們也不經(jīng)常稱為"completion"
或"error"
。相反钉答,我們用then(..)
來注冊(cè)一個(gè)"then"
事件础芍。或者也許更準(zhǔn)確地講数尿,then(..)
注冊(cè)了"fulfillment(完成)"
和/或"rejection(拒絕)"
事件仑性,雖然我們?cè)诖a中不會(huì)看到這些名詞被明確地使用。
考慮:
function foo(x) {
// 開始做一些可能會(huì)花一段時(shí)間的事情
// 構(gòu)建并返回一個(gè)promise
return new Promise( function(resolve,reject){
// 最終需要調(diào)用`resolve(..)`或`reject(..)`
// 它們是這個(gè)promise的解析回調(diào)
} );
}
var p = foo( 42 );
bar( p );
baz( p );
注意: 在new Promise( function(..){ .. } )
中展示的模式通常被稱為“揭示構(gòu)造器(revealing constructor)”右蹦。被傳入的函數(shù)被立即執(zhí)行(不會(huì)被異步推遲诊杆,像then(..)
的回調(diào)那樣),而且它被提供了兩個(gè)參數(shù)何陆,我們叫它們resolve
和reject
晨汹。這些是Promise的解析函數(shù)。resolve(..)
一般表示完成贷盲,而reject(..)
表示拒絕淘这。
你可能猜到了bar(..)
和baz(..)
的內(nèi)部看起來是什么樣子:
function bar(fooPromise) {
// 監(jiān)聽`foo(..)`的完成
fooPromise.then(
function(){
// `foo(..)`現(xiàn)在完成了剥扣,那么做`bar(..)`的任務(wù)
},
function(){
// 噢,在`foo(..)`中有某些事情搞錯(cuò)了
}
);
}
// `baz(..)`同上
Promise解析沒有必要一定發(fā)送消息铝穷,就像我們將Promise作為 未來值 考察時(shí)那樣钠怯。它可以僅僅作為一種流程控制信號(hào),就像前面的代碼中那樣使用曙聂。
另一種表達(dá)方式是:
function bar() {
// `foo(..)`絕對(duì)已經(jīng)完成了晦炊,那么做`bar(..)`的任務(wù)
}
function oopsBar() {
// 噢,在`foo(..)`中有某些事情搞錯(cuò)了宁脊,那么`bar(..)`不會(huì)運(yùn)行
}
// `baz()`和`oopsBaz()`同上
var p = foo( 42 );
p.then( bar, oopsBar );
p.then( baz, oopsBaz );
注意: 如果你以前見過基于Promise的代碼断国,你可能會(huì)相信這段代碼的最后兩行應(yīng)當(dāng)寫做p.then( .. ).then( .. )
,使用鏈接榆苞,而不是p.then(..); p.then(..)
稳衬。這將會(huì)是兩種完全不同的行為,所以要小心语稠!這種區(qū)別現(xiàn)在看起來可能不明顯宋彼,但是它們實(shí)際上是我們目前還沒有見過的異步模式:分割(splitting)/分叉(forking)。不必?fù)?dān)心仙畦!本章后面我們會(huì)回到這個(gè)話題输涕。
與將p
promise傳入bar(..)
和baz(..)
相反,我們使用promise來控制bar(..)
和baz(..)
何時(shí)該運(yùn)行慨畸,如果有這樣的時(shí)刻莱坎。主要區(qū)別在于錯(cuò)誤處理。
在第一個(gè)代碼段的方式中寸士,無論foo(..)
是否成功bar(..)
都會(huì)被調(diào)用檐什,如果被通知foo(..)
失敗了的話它提供自己的后備邏輯。顯然弱卡,baz(..)
也是這樣做的乃正。
在第二個(gè)代碼段中,bar(..)
僅在foo(..)
成功后才被調(diào)用婶博,否則oopsBar(..)
會(huì)被調(diào)用瓮具。baz(..)
也是。
兩種方式本身都 對(duì)凡人。但會(huì)有一些情況使一種優(yōu)于另一種名党。
在這兩種方式中,從foo(..)
返回的promisep
都被用于控制下一步發(fā)生什么挠轴。
另外传睹,兩個(gè)代碼段都以對(duì)同一個(gè)promisep
調(diào)用兩次then(..)
結(jié)束,這展示了先前的觀點(diǎn)岸晦,也就是Promise(一旦被解析)會(huì)永遠(yuǎn)保持相同的解析結(jié)果(完成或拒絕)欧啤,而且可以按需要后續(xù)地被監(jiān)聽任意多次睛藻。
無論何時(shí)p
被解析,下一步都將總是相同的堂油,包括 現(xiàn)在 和 稍后修档。
Thenable鴨子類型(Duck Typing)
在Promise的世界中碧绞,一個(gè)重要的細(xì)節(jié)是如何確定一個(gè)值是否是純粹的Promise府框。或者更直接地說讥邻,一個(gè)值會(huì)不會(huì)像Promise那樣動(dòng)作迫靖?
我們知道Promise是由new Promise(..)
語法構(gòu)建的,你可能會(huì)想p instanceof Promise
將是一個(gè)可以接受的檢查兴使。但不幸的是系宜,有幾個(gè)理由表明它不是完全夠用。
主要原因是发魄,你可以從其他瀏覽器窗口中收到Promise值(iframe等)盹牧,其他的瀏覽器窗口會(huì)擁有自己的不同于當(dāng)前窗口/frame的Promise,這種檢查將會(huì)在定位Promise實(shí)例時(shí)失效励幼。
另外汰寓,一個(gè)庫或框架可能會(huì)選擇實(shí)現(xiàn)自己的Promise而不是用ES6原生的Promise
實(shí)現(xiàn)。事實(shí)上苹粟,你很可能在根本沒有Promise的老版本瀏覽器中通過一個(gè)庫來使用Promise有滑。
當(dāng)我們?cè)诒菊律院笥懻揚(yáng)romise的解析過程時(shí),為什么識(shí)別并同化一個(gè)非純種但相似Promise的值仍然很重要會(huì)愈發(fā)明顯嵌削。但目前只需要相信我毛好,它是拼圖中很重要的一塊。
如此苛秕,人們決定識(shí)別一個(gè)Promise(或像Promise一樣動(dòng)作的某些東西)的方法是定義一種稱為“thenable”的東西肌访,也就是任何擁有then(..)
方法的對(duì)象或函數(shù)。這種方法假定任何這樣的值都是一個(gè)符合Promise的thenable艇劫。
根據(jù)值的形狀(存在什么屬性)來推測(cè)它的“類型”的“類型檢查”有一個(gè)一般的名稱吼驶,稱為“鴨子類型檢查”——“如果它看起來像一只鴨子,并且叫起來相一致鴨子港准,那么它一定是一只鴨子”(參見本叢書的 類型與文法)旨剥。所以對(duì)thenable的鴨子類型檢查可能大致是這樣:
if (
p !== null &&
(
typeof p === "object" ||
typeof p === "function"
) &&
typeof p.then === "function"
) {
// 認(rèn)為它是一個(gè)thenable!
}
else {
// 不是一個(gè)thenable
}
暈!先把將這種邏輯在各種地方實(shí)現(xiàn)有點(diǎn)丑陋的事實(shí)放在一邊不談浅缸,這里還有更多更深層的麻煩轨帜。
如果你試著用一個(gè)偶然擁有then(..)
函數(shù)的任意對(duì)象/函數(shù)來完成一個(gè)Promise,但你又沒想把它當(dāng)做一個(gè)Promise/thenable來對(duì)待衩椒,你的運(yùn)氣就用光了蚌父,因?yàn)樗鼤?huì)被自動(dòng)地識(shí)別為一個(gè)thenable并以特殊的規(guī)則來對(duì)待(見本章后面的部分)哮兰。
如果你不知道一個(gè)值上面擁有then(..)
就更是這樣。比如:
var o = { then: function(){} };
// 使`v`用`[[Prototype]]`鏈接到`o`
var v = Object.create( o );
v.someStuff = "cool";
v.otherStuff = "not so cool";
v.hasOwnProperty( "then" ); // false
v
看起來根本不像是一個(gè)Promise或thanable苟弛。它只是一個(gè)擁有一些屬性的直白的對(duì)象。你可能只是想要把這個(gè)值像其他對(duì)象那樣傳遞而已缤削。
但你不知道的是让腹,v
還[[Prototype]]
連接著(見本叢書的 this與對(duì)象原型)另一個(gè)對(duì)象o
,在它上面偶然擁有一個(gè)then(..)
蛔钙。所以thenable鴨子類型檢查將會(huì)認(rèn)為并假定v
是一個(gè)thenable娃胆。噢州泊。
它甚至不需要直接故意那么做:
Object.prototype.then = function(){};
Array.prototype.then = function(){};
var v1 = { hello: "world" };
var v2 = [ "Hello", "World" ];
v1
和v2
都將被假定為是thenalbe的。你不能控制或預(yù)測(cè)是否有其他代碼偶然或惡意地將then(..)
加到Object.prototype
没讲,Array.prototype
穿剖,或其他任何原生原型上。而且如果這個(gè)指定的函數(shù)并不將它的任何參數(shù)作為回調(diào)調(diào)用,那么任何用這樣的值被解析的Promise都將無聲地永遠(yuǎn)掛起壹甥!瘋狂句柠。
聽起來難以置信或不太可能?也許顶伞。
要知道,在ES6之前就有幾種廣為人知的非Promise庫在社區(qū)中存在了阻荒,而且它們已經(jīng)偶然擁有了稱為then(..)
的方法挠锥。這些庫中的一些選擇了重命名它們自己的方法來回避沖突(這很爛!)侨赡。另一些則因?yàn)樗鼈儫o法改變來回避沖突蓖租,簡(jiǎn)單地降級(jí)為“不兼容基于Promise的代碼”的不幸狀態(tài)。
用來劫持原先非保留的——而且聽起來完全是通用的——then
屬性名稱的標(biāo)準(zhǔn)決議是羊壹,沒有值(或它的任何委托)蓖宦,無論是過去,現(xiàn)在油猫,還是將來稠茂,可以擁有then(..)
函數(shù),不管是有意的還是偶然的,否則這個(gè)值將在Promise系統(tǒng)中被混淆為一個(gè)thenable睬关,從而可能產(chǎn)生非常難以追蹤的Bug诱担。
警告: 我不喜歡我們用thenable的鴨子類型來結(jié)束對(duì)Promise認(rèn)知的方式。還有其他的選項(xiàng)电爹,比如“branding”或者甚至是“anti-branding”蔫仙;我們得到的似乎是一個(gè)最差勁兒的妥協(xié)。但它并不全是悲觀與失望丐箩。thenable鴨子類型可以很有用摇邦,就像我們馬上要看到的。只是要小心屎勘,如果thenable鴨子類型將不是Promise的東西誤認(rèn)為是Promise施籍,它就可能成為災(zāi)難。
Promise的信任
我們已經(jīng)看過了兩個(gè)強(qiáng)烈的類比概漱,它們解釋了Promise可以為我們的異步代碼所做的事的不同方面丑慎。但如果我們停在這里,我們就可能會(huì)錯(cuò)過一個(gè)Promise模式建立的最重要的性質(zhì):信任犀概。
隨著 未來值 和 完成事件 的類別在我們探索的代碼模式中的明確展開立哑,有一個(gè)問題依然沒有完全明確:Promise是為什么,以及如何被設(shè)計(jì)為來解決所有我們?cè)诘诙隆靶湃螁栴}”一節(jié)中提出的 控制倒轉(zhuǎn) 的信任問題的姻灶。但是只要深挖一點(diǎn)兒,我們就可以發(fā)現(xiàn)一些重要的保證诈茧,來重建第二章中毀掉的對(duì)異步代碼的信心产喉!
讓我們從復(fù)習(xí)僅使用回調(diào)的代碼中的信任問題開始。當(dāng)你傳遞一個(gè)回調(diào)給一個(gè)工具foo(..)
的時(shí)候敢会,它可能:
- 調(diào)用回調(diào)太早
- 調(diào)用回調(diào)太晚(或根本不調(diào))
- 調(diào)用回調(diào)太少或太多次
- 沒能傳遞必要的環(huán)境/參數(shù)
- 吞掉了任何可能發(fā)生的錯(cuò)誤/異常
Promise的性質(zhì)被有意地設(shè)計(jì)為給這些顧慮提供有用的曾沈,可復(fù)用的答案。
調(diào)的太早
這種顧慮主要是代碼是否會(huì)引入類Zalgo效應(yīng)鸥昏,也就是一個(gè)任務(wù)有時(shí)會(huì)同步完地成塞俱,而有時(shí)會(huì)異步地完成,這將導(dǎo)致竟合狀態(tài)吏垮。
Promise被定義為不能受這種顧慮的影響障涯,因?yàn)榧幢闶橇⒓赐瓿傻腜romise(比如 new Promise(function(resolve){ resolve(42); })
)也不可能被同步地 監(jiān)聽。
也就是說膳汪,但你在Promise上調(diào)用then(..)
的時(shí)候唯蝶,即便這個(gè)Promise已經(jīng)被解析了,你給then(..)
提供的回調(diào)也將 總是 被異步地調(diào)用(更多關(guān)于這里的內(nèi)容遗嗽,參照第一章的"Jobs")粘我。
不必再插入你自己的setTimeout(..,0)
黑科技了。Promise自動(dòng)地防止了Zalgo效應(yīng)痹换。
調(diào)的太晚
和前一點(diǎn)相似征字,在resolve(..)
或reject(..)
被Promise創(chuàng)建機(jī)制調(diào)用時(shí)都弹,一個(gè)Promise的then(..)
上注冊(cè)的監(jiān)聽回調(diào)將自動(dòng)地被排程。這些被排程好的回調(diào)將在下一個(gè)異步時(shí)刻被可預(yù)測(cè)地觸發(fā)(參照第一章的"Jobs")匙姜。
同步監(jiān)聽是不可能的缔杉,所以不可能有一個(gè)同步的任務(wù)鏈的運(yùn)行來“推遲”另一個(gè)回調(diào)的發(fā)生。也就是說搁料,當(dāng)一個(gè)Promise被解析時(shí)或详,所有在then(..)
上注冊(cè)的回調(diào)都將被立即,按順序地郭计,在下一個(gè)異步機(jī)會(huì)時(shí)被調(diào)用(再一次霸琴,參照第一章的"Jobs"),而且沒有任何在這些回調(diào)中發(fā)生的事情可以影響/推遲其他回調(diào)的調(diào)用昭伸。
舉例來說:
p.then( function(){
p.then( function(){
console.log( "C" );
} );
console.log( "A" );
} );
p.then( function(){
console.log( "B" );
} );
// A B C
這里梧乘,有賴于Promise如何定義操作,"C"
不可能干擾并優(yōu)先于"B"
庐杨。
Promise排程的怪現(xiàn)象
重要并需要注意的是选调,排程有許多微妙的地方:鏈接在兩個(gè)分離的Promise上的回調(diào)之間的相對(duì)順序,是不能可靠預(yù)測(cè)的灵份。
如果兩個(gè)promisep1
和p2
都準(zhǔn)備好被解析了仁堪,那么p1.then(..); p2.then(..)
應(yīng)當(dāng)歸結(jié)為首先調(diào)用p1
的回調(diào),然后調(diào)用p2
的填渠。但有一些微妙的情形可能會(huì)使這不成立弦聂,比如下面這樣:
var p3 = new Promise( function(resolve,reject){
resolve( "B" );
} );
var p1 = new Promise( function(resolve,reject){
resolve( p3 );
} );
var p2 = new Promise( function(resolve,reject){
resolve( "A" );
} );
p1.then( function(v){
console.log( v );
} );
p2.then( function(v){
console.log( v );
} );
// A B <-- 不是你可能期望的 B A
我們稍后會(huì)更多地講解這個(gè)問題,但如你所見氛什,p1
不是被一個(gè)立即值所解析的莺葫,而是由另一個(gè)promisep3
所解析,而p3
本身被一個(gè)值"B"
所解析枪眉。這種指定的行為將p3
展開 到p1
捺檬,但是是異步地,所以在異步工作隊(duì)列中p1
的回調(diào)位于p2
的回調(diào)之后(參照第一章的"Jobs")贸铜。
為了回避這樣的微妙的噩夢(mèng)堡纬,你絕不應(yīng)該依靠任何跨Promise的回調(diào)順序/排程。事實(shí)上萨脑,一個(gè)好的實(shí)踐方式是在代碼中根本不要讓多個(gè)回調(diào)的順序成為問題隐轩。盡可能回避它。
根本不調(diào)回調(diào)
這是一個(gè)很常見的顧慮渤早。Promise用幾種方式解決它职车。
首先,沒有任何東西(JS錯(cuò)誤都不能)可以阻止一個(gè)Promise通知你它的解析(如果它被解析了的話)。如果你在一個(gè)Promise上同時(shí)注冊(cè)了完成和拒絕回調(diào)悴灵,而且這個(gè)Promise被解析了扛芽,兩個(gè)回調(diào)中的一個(gè)總會(huì)被調(diào)用。
當(dāng)然积瞒,如果你的回調(diào)本身有JS錯(cuò)誤川尖,你可能不會(huì)看到你期望的結(jié)果,但是回調(diào)事實(shí)上已經(jīng)被調(diào)用了茫孔。我們待會(huì)兒就會(huì)講到如何在你的回調(diào)中收到關(guān)于一個(gè)錯(cuò)誤的通知叮喳,因?yàn)榫退闶撬鼈円膊粫?huì)被吞掉。
那如果Promise本身不管怎樣永遠(yuǎn)沒有被解析呢缰贝?即便是這種狀態(tài)Promise也給出了答案馍悟,使用一個(gè)稱為“競(jìng)賽(race)”的高級(jí)抽象。
// 一個(gè)使Promise超時(shí)的工具
function timeoutPromise(delay) {
return new Promise( function(resolve,reject){
setTimeout( function(){
reject( "Timeout!" );
}, delay );
} );
}
// 為`foo()`設(shè)置一個(gè)超時(shí)
Promise.race( [
foo(), // 嘗試調(diào)用`foo()`
timeoutPromise( 3000 ) // 給它3秒鐘
] )
.then(
function(){
// `foo(..)`及時(shí)地完成了剩晴!
},
function(err){
// `foo()`不是被拒絕了锣咒,就是它沒有及時(shí)完成
// 那么可以考察`err`來知道是哪種情況
}
);
這種Promise的超時(shí)模式有更多的細(xì)節(jié)需要考慮,但我們待會(huì)兒再回頭討論赞弥。
重要的是毅整,我們可以確保一個(gè)信號(hào)作為foo(..)
的結(jié)果,來防止它無限地掛起我們的程序绽左。
調(diào)太少或太多次
根據(jù)定義悼嫉,對(duì)于被調(diào)用的回調(diào)來講 一次 是一個(gè)合適的次數(shù)「玖猓“太少”的情況將會(huì)是0次承粤,和我們剛剛考察的從不調(diào)用是相同的。
“太多”的情況則很容易解釋闯团。Promise被定義為只能被解析一次。如果因?yàn)槟承┰蛳闪唬琍romise的創(chuàng)建代碼試著調(diào)用resolve(..)
或reject(..)
許多次房交,或者試著同時(shí)調(diào)用它們倆,Promise將僅接受第一次解析伐割,而無聲地忽略后續(xù)的嘗試候味。
因?yàn)橐粋€(gè)Promise僅能被解析一次,所以任何then(..)
上注冊(cè)的(每個(gè))回調(diào)將僅僅被調(diào)用一次隔心。
當(dāng)然白群,如果你把同一個(gè)回調(diào)注冊(cè)多次(比如p.then(f); p.then(f);
),那么它就會(huì)被調(diào)用注冊(cè)的那么多次硬霍。響應(yīng)函數(shù)僅被調(diào)用一次的保證并不能防止你砸自己的腳帜慢。
沒能傳入任何參數(shù)/環(huán)境
Promise可以擁有最多一個(gè)解析值(完成或拒絕)。
如果無論怎樣你沒有用一個(gè)值明確地解析它,它的值就是undefined
粱玲,就像JS中常見的那樣躬柬。但不管是什么值,它總是會(huì)被傳入所有被注冊(cè)的(并且適當(dāng)?shù)兀和瓿苫蚓芙^)回調(diào)中抽减,不管是 現(xiàn)在 還是將來允青。
需要意識(shí)到的是:如果你使用多個(gè)參數(shù)調(diào)用resolve(..)
或reject(..)
,所有第一個(gè)參數(shù)之外的后續(xù)參數(shù)都會(huì)被無聲地忽略卵沉。雖然這看起來違反了我們剛才描述的保證颠锉,但并不確切,因?yàn)樗鼧?gòu)成了一種Promise機(jī)制的無效使用方式史汗。其他的API無效使用方式(比如調(diào)用resolve(..)
許多次)也都相似地 被保護(hù)琼掠,所以Promise的行為在這里是一致的(除了有一點(diǎn)點(diǎn)讓人沮喪)。
如果你想傳遞多個(gè)值淹办,你必須將它們包裝在另一個(gè)單獨(dú)的值中眉枕,比如一個(gè)array
或一個(gè)object
。
至于環(huán)境怜森,JS中的函數(shù)總是保持他們被定義時(shí)所在作用域的閉包(見本系列的 作用域與閉包)速挑,所以它們理所當(dāng)然地可以繼續(xù)訪問你提供的環(huán)境狀態(tài)。當(dāng)然副硅,這對(duì)僅使用回調(diào)的設(shè)計(jì)來講也是對(duì)的姥宝,所以這不能算是Promise帶來的增益——但盡管如此,它依然是我們可以依賴的保證恐疲。
吞掉所有錯(cuò)誤/異常
在基本的感覺上腊满,這是前一點(diǎn)的重述。如果你用一個(gè) 理由(也就是錯(cuò)誤消息)拒絕一個(gè)Promise培己,這個(gè)值就會(huì)被傳入拒絕回調(diào)碳蛋。
但是這里有一個(gè)更重要的事情。如果在Promise的創(chuàng)建過程中的任意一點(diǎn)省咨,或者在監(jiān)聽它的解析的過程中肃弟,一個(gè)JS異常錯(cuò)誤發(fā)生的話,比如TypeError
或ReferenceError
零蓉,這個(gè)異常將會(huì)被捕獲笤受,并且強(qiáng)制當(dāng)前的Promise變?yōu)榫芙^。
舉例來說:
var p = new Promise( function(resolve,reject){
foo.bar(); // `foo`沒有定義敌蜂,所以這是一個(gè)錯(cuò)誤箩兽!
resolve( 42 ); // 永遠(yuǎn)不會(huì)跑到這里 :(
} );
p.then(
function fulfilled(){
// 永遠(yuǎn)不會(huì)跑到這里 :(
},
function rejected(err){
// `err`將是一個(gè)來自`foo.bar()`那一行的`TypeError`異常對(duì)象
}
);
在foo.bar()
上發(fā)生的JS異常變成了一個(gè)你可以捕獲并響應(yīng)的Promise拒絕。
這是一個(gè)重要的細(xì)節(jié)章喉,因?yàn)樗行У亟鉀Q了另一種潛在的Zalgo時(shí)刻汗贫,也就是錯(cuò)誤可能會(huì)產(chǎn)生一個(gè)同步的反應(yīng)身坐,而沒有錯(cuò)誤的部分還是異步的。Promise甚至將JS異常都轉(zhuǎn)化為異步行為芳绩,因此極大地降低了發(fā)生竟合狀態(tài)的可能性掀亥。
但是如果Promise完成了,但是在監(jiān)聽過程中(在一個(gè)then(..)
上注冊(cè)的回調(diào)上)出現(xiàn)了JS異常錯(cuò)誤會(huì)怎樣呢妥色?即便是那些也不會(huì)丟失搪花,但你可能會(huì)發(fā)現(xiàn)處理它們的方式有些令人詫異,除非你深挖一些:
var p = new Promise( function(resolve,reject){
resolve( 42 );
} );
p.then(
function fulfilled(msg){
foo.bar();
console.log( msg ); // 永遠(yuǎn)不會(huì)跑到這里 :(
},
function rejected(err){
// 也永遠(yuǎn)不會(huì)跑到這里 :(
}
);
等一下嘹害,這看起來foo.bar()
發(fā)生的異常確實(shí)被吞掉了撮竿。不要害怕,它沒有笔呀。但更深層次的東西出問題了幢踏,也就是我們沒能成功地監(jiān)聽他。p.then(..)
調(diào)用本身返回另一個(gè)promise许师,是 那個(gè) promise將會(huì)被TypeError
異常拒絕房蝉。
為什么它不能調(diào)用我們?cè)谶@里定義的錯(cuò)誤處理器呢?表面上看起來是一個(gè)符合邏輯的行為微渠。但它會(huì)違反Promise一旦被解析就 不可變 的基本原則搭幻。p
已經(jīng)完成為值42
,所以它不能因?yàn)樵诒O(jiān)聽p
的解析時(shí)發(fā)生了錯(cuò)誤逞盆,而在稍后變成一個(gè)拒絕檀蹋。
除了違反原則,這樣的行為還可能造成破壞云芦,假如說有多個(gè)在promisep
上注冊(cè)的then(..)
回調(diào)俯逾,因?yàn)橛行?huì)被調(diào)用而有些不會(huì),而且至于為什么是很明顯的舅逸。
可信的Promise桌肴?
為了基于Promise模式建立信任,還有最后一個(gè)細(xì)節(jié)需要考察琉历。
無疑你已經(jīng)注意到了识脆,Promise根本沒有擺脫回調(diào)。它們只是改變了回調(diào)傳遞的位置善已。與將一個(gè)回調(diào)傳入foo(..)
相反馍乙,我們從foo(..)
那里拿回 某些東西 (表面上是一個(gè)純粹的Promise)蕴掏,然后我們將回調(diào)傳入這個(gè) 東西芜果。
但為什么這要比僅使用回調(diào)的方式更可靠呢康吵?我們?nèi)绾未_信我們拿回來的 某些東西 事實(shí)上是一個(gè)可信的Promise兔毙?這難道不是說我們相信它僅僅因?yàn)槲覀円呀?jīng)相信它了嗎托酸?
一個(gè)Promise經(jīng)常被忽視吊履,但是最重要的細(xì)節(jié)之一寨典,就是它也為這個(gè)問題給出了解決方案。包含在原生的ES6Promise
實(shí)現(xiàn)中想虎,它就是Promise.resolve(..)
卦尊。
如果你傳遞一個(gè)立即的,非Promise的舌厨,非thenable的值給Promise.resolve(..)
岂却,你會(huì)得到一個(gè)用這個(gè)值完成的promise。換句話說裙椭,下面兩個(gè)promisep1
和p2
的行為基本上完全相同:
var p1 = new Promise( function(resolve,reject){
resolve( 42 );
} );
var p2 = Promise.resolve( 42 );
但如果你傳遞一個(gè)純粹的Promise給Promise.resolve(..)
躏哩,你會(huì)得到這個(gè)完全相同的promise:
var p1 = Promise.resolve( 42 );
var p2 = Promise.resolve( p1 );
p1 === p2; // true
更重要的是,如果你傳遞一個(gè)非Promise的thenable值給Promise.resolve(..)
揉燃,它會(huì)試著將這個(gè)值展開扫尺,而且直到抽出一個(gè)最終具體的非Promise值之前,展開操作將會(huì)一直繼續(xù)下去炊汤。
還記得我們先前討論的thenable嗎正驻?
考慮這段代碼:
var p = {
then: function(cb) {
cb( 42 );
}
};
// 這工作起來沒問題,但要靠運(yùn)氣
p
.then(
function fulfilled(val){
console.log( val ); // 42
},
function rejected(err){
// 永遠(yuǎn)不會(huì)跑到這里
}
);
這個(gè)p
是一個(gè)thenable抢腐,但它不是一個(gè)純粹的Promise姑曙。很走運(yùn),它是合理的氓栈,正如大多數(shù)情況那樣渣磷。但是如果你得到的是看起來像這樣的東西:
var p = {
then: function(cb,errcb) {
cb( 42 );
errcb( "evil laugh" );
}
};
p
.then(
function fulfilled(val){
console.log( val ); // 42
},
function rejected(err){
// 噢,這里本不該運(yùn)行
console.log( err ); // evil laugh
}
);
這個(gè)p
是一個(gè)thenable授瘦,但它不是表現(xiàn)良好的promise醋界。它是惡意的嗎?或者它只是不知道Promise應(yīng)當(dāng)如何工作提完?老實(shí)說形纺,這不重要。不管哪種情況徒欣,它都不那么可靠逐样。
盡管如此,我們可以將這兩個(gè)版本的p
傳入Promise.resolve(..)
打肝,而且我們將會(huì)得到一個(gè)我們期望的泛化脂新,安全的結(jié)果:
Promise.resolve( p )
.then(
function fulfilled(val){
console.log( val ); // 42
},
function rejected(err){
// 永遠(yuǎn)不會(huì)跑到這里
}
);
Promise.resolve(..)
會(huì)接受任何thenable,而且將它展開直至非thenable值粗梭。但你會(huì)從Promise.resolve(..)
那里得到一個(gè)真正的争便,純粹的Promise,一個(gè)你可以信任的東西断医。如果你傳入的東西已經(jīng)是一個(gè)純粹的Promise了滞乙,那么你會(huì)單純地將它拿回來奏纪,所以通過Promise.resolve(..)
過濾來得到信任沒有任何壞處。
那么我們假定斩启,我們?cè)谡{(diào)用一個(gè)foo(..)
工具序调,而且不能確定我們能相信它的返回值是一個(gè)行為規(guī)范的Promise,但我們知道它至少是一個(gè)thenable兔簇。Promise.resolve(..)
將會(huì)給我們一個(gè)可靠的Promise包裝器來進(jìn)行鏈?zhǔn)秸{(diào)用:
// 不要只是這么做:
foo( 42 )
.then( function(v){
console.log( v );
} );
// 相反发绢,這樣做:
Promise.resolve( foo( 42 ) )
.then( function(v){
console.log( v );
} );
注意: 將任意函數(shù)的返回值(thenable或不是thenable)包裝在Promise.resolve(..)
中的另一個(gè)好的副作用是,它可以很容易地將函數(shù)調(diào)用泛化為一個(gè)行為規(guī)范的異步任務(wù)男韧。如果foo(42)
有時(shí)返回一個(gè)立即值朴摊,而其他時(shí)候返回一個(gè)Promise,Promise.resolve(foo(42))
此虑,將確保它總是返回Promise甚纲。并且使代碼成為回避Zalgo效應(yīng)的更好的代碼。
信任建立了
希望前面的討論使你現(xiàn)在完全理解了Promise是可靠的朦前,而且更為重要的是介杆,為什么信任對(duì)于建造強(qiáng)壯,可維護(hù)的軟件來說是如此關(guān)鍵韭寸。
沒有信任春哨,你能用JS編寫異步代碼嗎?你當(dāng)然能恩伺。我們JS開發(fā)者在除了回調(diào)以外沒有任何東西的情況下赴背,寫了將近20年的異步代碼了。
但是一旦你開始質(zhì)疑你到底能夠以多大的程度相信你的底層機(jī)制晶渠,它實(shí)際上多么可預(yù)見凰荚,多么可靠,你就會(huì)開始理解回調(diào)的信任基礎(chǔ)多么的搖搖欲墜褒脯。
Promise是一個(gè)用可靠語義來增強(qiáng)回調(diào)的模式便瑟,所以它的行為更合理更可靠。通過將回調(diào)的 控制倒轉(zhuǎn) 反置過來番川,我們將控制交給一個(gè)可靠的系統(tǒng)(Promise)到涂,它是為了將你的異步處理進(jìn)行清晰的表達(dá)而特意設(shè)計(jì)的。
鏈?zhǔn)搅鞒?/h2>
我們已經(jīng)被暗示過幾次颁督,但Promise不僅是是一個(gè)單步的 這個(gè)然后那個(gè) 操作機(jī)制践啄。當(dāng)然,那是構(gòu)建塊兒沉御,但事實(shí)證明我們可以將多個(gè)Promise串聯(lián)在一起來表達(dá)一系列的異步步驟往核。
使這一切能夠工作的關(guān)鍵,是Promise的兩個(gè)固有行為:
- 每次你在一個(gè)Promise上調(diào)用
then(..)
的時(shí)候嚷节,它都創(chuàng)建并返回一個(gè)新的Promise聂儒,我們可以在它上面進(jìn)行 鏈接。 - 無論你從
then(..)
調(diào)用的完成回調(diào)中(第一個(gè)參數(shù))返回什么值硫痰,它都做為被鏈接的Promise的完成衩婚。
我們首先來說明一下這是什么意思,然后我們將會(huì)延伸出它是如何幫助我們創(chuàng)建異步順序的控制流程的效斑》谴海考慮下面的代碼:
var p = Promise.resolve( 21 );
var p2 = p.then( function(v){
console.log( v ); // 21
// 使用值`42`完成`p2`
return v * 2;
} );
// 在`p2`后鏈接
p2.then( function(v){
console.log( v ); // 42
} );
通過返回v * 2
(也就是42
),我們完成了由第一個(gè)then(..)
調(diào)用創(chuàng)建并返回的p2
promise缓屠。當(dāng)p2
的then(..)
調(diào)用運(yùn)行時(shí)奇昙,它從return v * 2
語句那里收到完成信號(hào)。當(dāng)然敌完,p2.then(..)
還會(huì)創(chuàng)建另一個(gè)promise储耐,我們將它存儲(chǔ)在變量p3
中。
但是不得不創(chuàng)建臨時(shí)變量p2
(或p3
等)有點(diǎn)兒惱人滨溉。幸運(yùn)的是什湘,我們可以簡(jiǎn)單地將這些鏈接在一起:
var p = Promise.resolve( 21 );
p
.then( function(v){
console.log( v ); // 21
// 使用值`42`完成被鏈接的promise
return v * 2;
} )
// 這里是被鏈接的promise
.then( function(v){
console.log( v ); // 42
} );
那么現(xiàn)在第一個(gè)then(..)
是異步序列的第一步,而第二個(gè)then(..)
就是第二步晦攒。它可以根據(jù)你的需要延伸至任意長(zhǎng)闽撤。只要持續(xù)不斷地用每個(gè)自動(dòng)創(chuàng)建的Promise在前一個(gè)then(..)
末尾進(jìn)行連接即可。
但是這里錯(cuò)過了某些東西脯颜。要是我們想讓第2步等待第1步去做一些異步的事情呢哟旗?我們使用的是一個(gè)立即的return
語句,它立即完成了鏈接中的promise栋操。
使Promise序列在每一步上都是真正異步的關(guān)鍵闸餐,需要回憶一下當(dāng)你向Promise.resolve(..)
傳遞一個(gè)Promise或thenable而非一個(gè)最終值時(shí)它如何執(zhí)行。Promise.resolve(..)
會(huì)直接返回收到的純粹Promise讼庇,或者它會(huì)展開收到的thenable的值——并且它會(huì)遞歸地持續(xù)展開thenable绎巨。
如果你從完成(或拒絕)處理器中返回一個(gè)thenable或Promise,同樣的展開操作也會(huì)發(fā)生蠕啄〕∏冢考慮這段代碼:
var p = Promise.resolve( 21 );
p.then( function(v){
console.log( v ); // 21
// 創(chuàng)建一個(gè)promise并返回它
return new Promise( function(resolve,reject){
// 使用值`42`完成
resolve( v * 2 );
} );
} )
.then( function(v){
console.log( v ); // 42
} );
即便我們把42
包裝在一個(gè)我們返回的promise中,它依然會(huì)被展開并作為下一個(gè)被鏈接的promise的解析歼跟,如此第二個(gè)then(..)
仍然收到42
和媳。如果我們?cè)谶@個(gè)包裝promise中引入異步,一切還是會(huì)同樣正常的工作:
var p = Promise.resolve( 21 );
p.then( function(v){
console.log( v ); // 21
// 創(chuàng)建一個(gè)promise并返回
return new Promise( function(resolve,reject){
// 引入異步哈街!
setTimeout( function(){
// 使用值`42`完成
resolve( v * 2 );
}, 100 );
} );
} )
.then( function(v){
// 在上一步中的100毫秒延遲之后運(yùn)行
console.log( v ); // 42
} );
這真是不可思議的強(qiáng)大留瞳!現(xiàn)在我們可以構(gòu)建一個(gè)序列,它可以有我們想要的任意多的步驟骚秦,而且每一步都可以按照需要來推遲下一步(或者不推遲)她倘。
當(dāng)然璧微,在這些例子中一步一步向下傳遞的值是可選的。如果你沒有返回一個(gè)明確的值硬梁,那么它假定一個(gè)隱含的undefined
前硫,而且promise依然會(huì)以同樣的方式鏈接在一起。如此荧止,每個(gè)Promise的解析只不過是進(jìn)行至下一步的信號(hào)屹电。
為了演示更長(zhǎng)的鏈接,讓我們把推遲Promise的創(chuàng)建(沒有解析信息)泛化為一個(gè)我們可以在多個(gè)步驟中復(fù)用的工具:
function delay(time) {
return new Promise( function(resolve,reject){
setTimeout( resolve, time );
} );
}
delay( 100 ) // step 1
.then( function STEP2(){
console.log( "step 2 (after 100ms)" );
return delay( 200 );
} )
.then( function STEP3(){
console.log( "step 3 (after another 200ms)" );
} )
.then( function STEP4(){
console.log( "step 4 (next Job)" );
return delay( 50 );
} )
.then( function STEP5(){
console.log( "step 5 (after another 50ms)" );
} )
...
調(diào)用delay(200)
創(chuàng)建了一個(gè)將在200毫秒內(nèi)完成的promise跃巡,然后我們?cè)诘谝粋€(gè)then(..)
的完成回調(diào)中返回它危号,這將使第二個(gè)then(..)
的promise等待這個(gè)200毫秒的promise。
注意: 正如剛才描述的素邪,技術(shù)上講在這個(gè)交替中有兩個(gè)promise:一個(gè)200毫秒延遲的promise外莲,和一個(gè)被第二個(gè)then(..)
鏈接的promise。但你可能會(huì)發(fā)現(xiàn)將這兩個(gè)promise組合在一起更容易思考娘香,因?yàn)镻romise機(jī)制幫你把它們的狀態(tài)自動(dòng)地混合到了一起苍狰。從這個(gè)角度講,你可以認(rèn)為return delay(200)
創(chuàng)建了一個(gè)promise來取代早前一個(gè)返回的被鏈接的promise烘绽。
老實(shí)說淋昭,沒有任何消息進(jìn)行傳遞的一系列延遲作為Promise流程控制的例子不是很有用。讓我們來看一個(gè)更加實(shí)在的場(chǎng)景:
與計(jì)時(shí)器不同安接,讓我們考慮發(fā)起Ajax請(qǐng)求:
// 假定一個(gè)`ajax( {url}, {callback} )`工具
// 帶有Promise的ajax
function request(url) {
return new Promise( function(resolve,reject){
// `ajax(..)`的回調(diào)應(yīng)當(dāng)是我們的promise的`resolve(..)`函數(shù)
ajax( url, resolve );
} );
}
我們首先定義一個(gè)request(..)
工具翔忽,它構(gòu)建一個(gè)promise表示ajax(..)
調(diào)用的完成:
request( "http://some.url.1/" )
.then( function(response1){
return request( "http://some.url.2/?v=" + response1 );
} )
.then( function(response2){
console.log( response2 );
} );
注意: 開發(fā)者們通常遭遇的一種情況是,他們想用本身不支持Promise的工具(就像這里的ajax(..)
盏檐,它期待一個(gè)回調(diào))進(jìn)行Promise式的異步流程控制歇式。雖然ES6原生的Promise
機(jī)制不會(huì)自動(dòng)幫我們解決這種模式,但是在實(shí)踐中所有的Promise庫會(huì)幫我們這么做胡野。它們通常稱這種處理為“提升(lifting)”或“promise化”或其他的什么名詞材失。我們稍后再回頭討論這種技術(shù)。
使用返回Promise的request(..)
硫豆,通過用第一個(gè)URL調(diào)用它我們?cè)阪湕l中隱式地創(chuàng)建了第一步龙巨,然后我們用第一個(gè)then(..)
在返回的promise末尾進(jìn)行連接。
一旦response1
返回熊响,我們用它的值來構(gòu)建第二個(gè)URL旨别,并且發(fā)起第二個(gè)request(..)
調(diào)用。這第二個(gè)promise
是return
的汗茄,所以我們的異步流程控制的第三步將會(huì)等待這個(gè)Ajax調(diào)用完成秸弛。最終,一旦response2
返回,我們就打印它递览。
我們構(gòu)建的Promise鏈不僅是一個(gè)表達(dá)多步驟異步序列的流程控制叼屠,它還扮演者將消息從一步傳遞到下一步的消息管道。
要是Promise鏈中的某一步出錯(cuò)了會(huì)怎樣呢非迹?一個(gè)錯(cuò)誤/異常是基于每個(gè)Promise的环鲤,意味著在鏈條的任意一點(diǎn)捕獲這些錯(cuò)誤是可能的,而且這些捕獲操作在那一點(diǎn)上將鏈條“重置”憎兽,使它回到正常的操作上來:
// 步驟 1:
request( "http://some.url.1/" )
// 步驟 2:
.then( function(response1){
foo.bar(); // 沒有定義,錯(cuò)誤吵冒!
// 永遠(yuǎn)不會(huì)跑到這里
return request( "http://some.url.2/?v=" + response1 );
} )
// 步驟 3:
.then(
function fulfilled(response2){
// 永遠(yuǎn)不會(huì)跑到這里
},
// 拒絕處理器捕捉錯(cuò)誤
function rejected(err){
console.log( err ); // 來自 `foo.bar()` 的 `TypeError` 錯(cuò)誤
return 42;
}
)
// 步驟 4:
.then( function(msg){
console.log( msg ); // 42
} );
當(dāng)錯(cuò)誤在第2步中發(fā)生時(shí)纯命,第3步的拒絕處理器將它捕獲。拒絕處理器的返回值(在這個(gè)代碼段里是42
)痹栖,如果有的話亿汞,將會(huì)完成下一步(第4步)的promise,如此整個(gè)鏈條又回到完成的狀態(tài)揪阿。
注意: 就像我們剛才討論過的疗我,當(dāng)我們從一個(gè)完成處理器中返回一個(gè)promise時(shí),它會(huì)被展開并有可能推遲下一步南捂。這對(duì)從拒絕處理器中返回的promise也是成立的吴裤,這樣如果我們?cè)诘?步返回一個(gè)promise而不是return 42
,那么這個(gè)promise就可能會(huì)推遲第4步溺健。不管是在then(..)
的完成還是拒絕處理器中麦牺,一個(gè)被拋出的異常都將導(dǎo)致下一個(gè)(鏈接著的)promise立即用這個(gè)異常拒絕。
如果你在一個(gè)promise上調(diào)用then(..)
鞭缭,而且你只向它傳遞了一個(gè)完成處理器剖膳,一個(gè)假定的拒絕處理器會(huì)取而代之:
var p = new Promise( function(resolve,reject){
reject( "Oops" );
} );
var p2 = p.then(
function fulfilled(){
// 永遠(yuǎn)不會(huì)跑到這里
}
// 如果忽略或者傳入任何非函數(shù)的值,
// 會(huì)有假定有一個(gè)這樣的拒絕處理器
// function(err) {
// throw err;
// }
);
如你所見岭辣,這個(gè)假定的拒絕處理器僅僅簡(jiǎn)單地重新拋出錯(cuò)誤吱晒,它最終強(qiáng)制p2
(鏈接著的promise)用同樣的錯(cuò)誤進(jìn)行拒絕。實(shí)質(zhì)上沦童,它允許錯(cuò)誤持續(xù)地在Promise鏈上傳播仑濒,直到遇到一個(gè)明確定義的拒絕處理器。
注意: 稍后我們會(huì)講到更多關(guān)于使用Promise進(jìn)行錯(cuò)誤處理的細(xì)節(jié)搞动,因?yàn)闀?huì)有更多微妙的細(xì)節(jié)需要關(guān)心躏精。
如果沒有一個(gè)恰當(dāng)?shù)暮戏ǖ暮瘮?shù)作為then(..)
的完成處理器參數(shù),也會(huì)有一個(gè)默認(rèn)的處理器取而代之:
var p = Promise.resolve( 42 );
p.then(
// 如果忽略或者傳入任何非函數(shù)的值鹦肿,
// 會(huì)有假定有一個(gè)這樣的完成處理器
// function(v) {
// return v;
// }
null,
function rejected(err){
// 永遠(yuǎn)不會(huì)跑到這里
}
);
如你所見矗烛,默認(rèn)的完成處理器簡(jiǎn)單地將它收到的任何值傳遞給下一步(Promise)。
注意: then(null,function(err){ .. })
這種模式——僅處理拒絕(如果發(fā)生的話)但讓成功通過——有一個(gè)縮寫的API:catch(function(err){ .. })
。我們會(huì)在下一節(jié)中更全面地涵蓋catch(..)
瞭吃。
然我們簡(jiǎn)要地復(fù)習(xí)一下使鏈?zhǔn)搅鞒炭刂瞥蔀榭赡艿腜romise固有行為:
- 在一個(gè)Promise上的
then(..)
調(diào)用會(huì)自動(dòng)生成一個(gè)新的Promise并返回碌嘀。 - 在完成/拒絕處理器內(nèi)部,如果你返回一個(gè)值或拋出一個(gè)異常歪架,新返回的Promise(可以被鏈接的)將會(huì)相應(yīng)地被解析股冗。
- 如果完成或拒絕處理器返回一個(gè)Promise,它會(huì)被展開和蚪,所以無論它被解析為什么值止状,這個(gè)值都將變成從當(dāng)前的
then(..)
返回的被鏈接的Promise的解析。
雖然鏈?zhǔn)搅鞒炭刂坪苡杏迷芘菍⑺J(rèn)為是Promise的組合方式的副作用可能最準(zhǔn)確怯疤,而不是它的主要意圖。正如我們已經(jīng)詳細(xì)討論過許多次的催束,Promise泛化了異步處理并且包裝了與時(shí)間相關(guān)的值和狀態(tài)集峦,這才是讓我們以這種有用的方式將它們鏈接在一起的原因。
當(dāng)然抠刺,相對(duì)于我們?cè)诘诙轮锌吹降囊欢鸦靵y的回調(diào)塔淤,這種鏈條的順序表達(dá)是一個(gè)巨大的改進(jìn)。但是仍然要蹚過相當(dāng)多的模板代碼(then(..)
and function(){ .. }
)速妖。在下一章中高蜂,我們將看到一種極大美化順序流程控制的表達(dá)模式,生成器(generators)买优。
術(shù)語: Resolve(解析)妨马,F(xiàn)ulfill(完成),和Reject(拒絕)
在你更多深入地學(xué)習(xí)Promise之前杀赢,在“解析(resolve)”烘跺,“完成(fulfill)”,和“拒絕(reject)”這些名詞之間還有一些我們需要辨明的小困惑脂崔。首先讓我們考慮一下Promise(..)
構(gòu)造器:
var p = new Promise( function(X,Y){
// X() 給 fulfillment(完成)
// Y() 給 rejection(拒絕)
} );
如你所見滤淳,有兩個(gè)回調(diào)(標(biāo)識(shí)為X
和Y
)被提供了。第一個(gè) 通常 用于表示Promise完成了砌左,而第二個(gè) 總是 表示Promise拒絕了脖咐。但“通常”是什么意思汇歹?它對(duì)這些參數(shù)的正確命名暗示著什么呢屁擅?
最終,這只是你的用戶代碼产弹,和將被引擎翻譯為沒有任何含義的東西的標(biāo)識(shí)符派歌,所以在 技術(shù)上 它無緊要;foo(..)
和bar(..)
在功能性上是相等的。但是你用的詞不僅會(huì)影響你如何考慮這段代碼胶果,還會(huì)影響你所在團(tuán)隊(duì)的其他開發(fā)者如何考慮它匾嘱。將精心策劃的異步代碼錯(cuò)誤地考慮,幾乎可以說要比面條一般的回調(diào)還要差勁兒早抠。
所以霎烙,某種意義上你如何稱呼它們很關(guān)鍵。
第二個(gè)參數(shù)很容易決定蕊连。幾乎所有的文獻(xiàn)都使用reject(..)
做為它的名稱悬垃,應(yīng)為這正是它(唯一!)要做的甘苍,對(duì)于命名來說這是一個(gè)很好的選擇盗忱。我也強(qiáng)烈推薦你一直使用reject(..)
。
但是關(guān)于第一個(gè)參數(shù)還是有些帶有歧義羊赵,它在許多關(guān)于Promise的文獻(xiàn)中常被標(biāo)識(shí)為resolve(..)
。這個(gè)詞明顯地是與“resolution(解析)”有關(guān)扇谣,它在所有的文獻(xiàn)中(包括本書)廣泛用于描述給Promise設(shè)定一個(gè)最終的值/狀態(tài)昧捷。我們已經(jīng)使用“解析Promise(resolve the Promise)”許多次來意味Promise的完成(fulfilling)或拒絕(rejecting)。
但是如果這個(gè)參數(shù)看起來被用于特指Promise的完成罐寨,為什么我們不更準(zhǔn)確地叫它fulfill(..)
靡挥,而是用resolve(..)
呢?要回答這個(gè)問題鸯绿,讓我們看一下Promise
的兩個(gè)API方法:
var fulfilledPr = Promise.resolve( 42 );
var rejectedPr = Promise.reject( "Oops" );
Promise.resolve(..)
創(chuàng)建了一個(gè)Promise跋破,它被解析為它被給予的值。在這個(gè)例子中瓶蝴,42
是一個(gè)一般的毒返,非Promise,非thenable的值舷手,所以完成的promisefulfilledPr
是為值42
創(chuàng)建的拧簸。Promise.reject("Oops")
為了原因"Oops"
創(chuàng)建的拒絕的promiserejectedPr
。
現(xiàn)在讓我們來解釋為什么如果“resolve”這個(gè)詞(正如Promise.resolve(..)
里的)被明確用于一個(gè)既可能完成也可能拒絕的環(huán)境時(shí)男窟,它沒有歧義盆赤,反而更加準(zhǔn)確:
var rejectedTh = {
then: function(resolved,rejected) {
rejected( "Oops" );
}
};
var rejectedPr = Promise.resolve( rejectedTh );
就像我們?cè)诒菊虑懊嬗懻摰模?code>Promise.resolve(..)將會(huì)直接返回收到的純粹的Promise,或者將收到的thenable展開歉眷。如果展開這個(gè)thenable之后是一個(gè)拒絕狀態(tài)牺六,那么從Promise.resolve(..)
返回的Promise事實(shí)上是相同的拒絕狀態(tài)。
所以對(duì)于這個(gè)API方法來說汗捡,Promise.resolve(..)
是一個(gè)好的淑际,準(zhǔn)確的名稱,因?yàn)樗鼘?shí)際上既可以得到完成的結(jié)果,也可以得到拒絕的結(jié)果庸追。
Promise(..)
構(gòu)造器的第一個(gè)回調(diào)參數(shù)既可以展開一個(gè)thenable(與Promise.resolve(..)
相同)霍骄,也可以展開一個(gè)Promise:
var rejectedPr = new Promise( function(resolve,reject){
// 用一個(gè)被拒絕的promise來解析這個(gè)promise
resolve( Promise.reject( "Oops" ) );
} );
rejectedPr.then(
function fulfilled(){
// 永遠(yuǎn)不會(huì)跑到這里
},
function rejected(err){
console.log( err ); // "Oops"
}
);
現(xiàn)在應(yīng)當(dāng)清楚了,對(duì)于Promise(..)
構(gòu)造器的第一個(gè)參數(shù)來說resolve(..)
是一個(gè)合適的名稱淡溯。
警告: 前面提到的reject(..)
不會(huì) 像resolve(..)
那樣進(jìn)行展開读整。如果你向reject(..)
傳遞一個(gè)Promise/thenable值,這個(gè)沒有被碰過的值將作為拒絕的理由咱娶。一個(gè)后續(xù)的拒絕處理器將會(huì)受到你傳遞給reject(..)
的實(shí)際的Promise/thenable米间,而不是它底層的立即值。
現(xiàn)在讓我們將注意力轉(zhuǎn)向提供給then(..)
的回調(diào)膘侮。它們應(yīng)當(dāng)叫什么(在文獻(xiàn)和代碼中)屈糊?我的建議是fulfilled(..)
和rejected(..)
:
function fulfilled(msg) {
console.log( msg );
}
function rejected(err) {
console.error( err );
}
p.then(
fulfilled,
rejected
);
對(duì)于then(..)
的第一個(gè)參數(shù)的情況,它沒有歧義地總是完成狀態(tài)琼了,所以沒有必要使用帶有雙重意義的“resolve”術(shù)語逻锐。另一方面,ES6語言規(guī)范中使用onFulfilled(..)
和onRejected(..)
來標(biāo)識(shí)這兩個(gè)回調(diào)雕薪,所以它們是準(zhǔn)確的術(shù)語昧诱。