摘要:直到最近,我們?nèi)匀辉谟煤?jiǎn)單的回調(diào)函數(shù)來處理異步的問題。當(dāng)我們只有一個(gè)異步任務(wù)的時(shí)候使用回調(diào)函數(shù)看起來還不會(huì)有什么問題。
原文地址:http://blog.getify.com/promis...
廈門旅行歸來,繼續(xù)理解Promise
在上一篇深入理解Promise五部曲:1.異步問題中,我們揭示了JS的異步事件輪詢并發(fā)模型并且解釋了多任務(wù)是如何相互穿插使得它們看起來像是同時(shí)運(yùn)行的。然后我們討論了為什么我們努力地在我們的代碼里表達(dá)這些東西以及為什么我們的大腦不善于理解它們。
我們現(xiàn)在要找出一個(gè)更好的方式來表達(dá)異步流程,然后看看Promises是怎么解決這個(gè)問題的。
回調(diào)嵌套JS從一開始就使用事件輪詢的并發(fā)模型。我們一直以來都在寫異步的程序。直到最近,我們?nèi)匀辉谟煤?jiǎn)單的回調(diào)函數(shù)來處理異步的問題。
makeAjaxRequest(url,function(respnose){ alert("Response:" + response) ; }) ;
當(dāng)我們只有一個(gè)異步任務(wù)的時(shí)候使用回調(diào)函數(shù)看起來還不會(huì)有什么問題。但是,實(shí)際是我們完成一個(gè)任務(wù)通常需要多個(gè)異步操作。例如:
btn.addEventListener("click",function(evt){ makeAjaxRequest(url,function(response){ makeAjaxRequest(anotherURL + "?resp=" + response,function(response2){ alert("Response2:" + response) ; }) }) ; },false) ;
把一系列異步操作鏈接在一起最自然的方式就是使用回調(diào)嵌套,步驟2嵌套在步驟1中然后步驟3嵌套在步驟2中,等等。
回調(diào)地獄你使用越多的回調(diào),就會(huì)有越多的嵌套,不斷縮進(jìn)意大利面條似的代碼。很顯然,這種代碼難以編寫,難以理解而且難以維護(hù)。如果我們花點(diǎn)時(shí)間來理清這些代碼往往會(huì)讓我們事半功倍。這類嵌套/縮進(jìn)經(jīng)常被叫做"回調(diào)地獄"。有時(shí)也被叫做"回調(diào)金字塔",專指由于代碼不斷縮進(jìn)所形成的金字塔形狀,縮進(jìn)越多金字塔形狀越明顯。
但是我還是覺得"回調(diào)地獄"真的跟嵌套和縮進(jìn)扯不上太大的關(guān)系。如果之前有人跟你說回調(diào)地獄就是指嵌套和縮進(jìn)的話,不要相信他,因?yàn)樗麄儾⒉焕斫饣卣{(diào)真正的問題在哪兒。
可靠性缺失回調(diào)(無論是否有嵌套)的真正問題是遠(yuǎn)比編輯器中的空白符嚴(yán)重。讓我們來分析下下面這個(gè)簡(jiǎn)單的回調(diào)發(fā)生了什么
//1.everything in my program before now someAsyncThing(function(){ //2.everything in my program for later }) ;
你看清這段代碼說了什么嗎?你從根本上把你的程序分成了兩個(gè)部分:
直到現(xiàn)在為止發(fā)生的事情
以后會(huì)發(fā)生的事情
換句話說,你把第二部分代碼包裝在一個(gè)回調(diào)函數(shù)中然后延遲到后面執(zhí)行。
但是這并不是問題,真正問題是在1和2之間發(fā)生了什么。請(qǐng)問在這段時(shí)間內(nèi)是誰(shuí)在控制這些。
someAsyncThing(..)控制著這些。是你自己擁有并管理someAsyncThing()嗎?許多時(shí)候不是。更重要的是,你有多信任someAsyncThing(..)?你會(huì)問,信任什么?不管你意識(shí)到?jīng)]有,你潛在的相信someAsyncThing(..)會(huì)做到下面這些:
不會(huì)太早調(diào)用我的回調(diào)函數(shù)
不會(huì)太遲調(diào)用我的回調(diào)函數(shù)(1,2就是說會(huì)在適當(dāng)?shù)臅r(shí)候調(diào)用回調(diào)函數(shù))
不會(huì)調(diào)用我的回調(diào)太少次(不會(huì)少于實(shí)際應(yīng)該調(diào)用的次數(shù),比如不會(huì)漏掉函數(shù)調(diào)用)
不會(huì)調(diào)用我的回調(diào)太多次(不會(huì)多于實(shí)際應(yīng)該調(diào)用的次數(shù),比如重復(fù)調(diào)用)
會(huì)給我的回調(diào)提供必要的參數(shù)
在我的回調(diào)失敗的時(shí)候會(huì)提醒我
咳!你也太信任它了!
實(shí)際上,這里真正的問題是由于回調(diào)引起的控制轉(zhuǎn)移。在你的程序的前半部分,你控制著程序的進(jìn)程?,F(xiàn)在你轉(zhuǎn)移了控制權(quán),someAsyncThing(..)控制了你剩余程序什么時(shí)候返回以及是否返回。控制轉(zhuǎn)移表明了你的代碼和其他人的代碼之間的過度信任關(guān)系。
恐嚇戰(zhàn)術(shù)當(dāng)someAsyncThing(..)是第三方庫(kù)的一個(gè)方法并且你無法控制不能檢查的時(shí)候會(huì)發(fā)生什么?只能祝你好運(yùn)了!
比如你有一個(gè)電子商務(wù)網(wǎng)站,用戶就要完成付款的步驟了,但是在扣費(fèi)之前有最后一個(gè)步驟,它需要通知一個(gè)第三方跟蹤庫(kù)。你調(diào)用他們API,并且提供一個(gè)回調(diào)函數(shù)。大部分情況下,這不會(huì)有什么問題。但是,在這次業(yè)務(wù)中,有一些你和他們都沒有意識(shí)到的奇怪的Bug,結(jié)果就是第三方庫(kù)在超時(shí)之前五秒的時(shí)間內(nèi)每隔一秒就會(huì)調(diào)用一次回調(diào)函數(shù)。猜猜發(fā)生了什么?在這個(gè)回調(diào)里調(diào)用了chargeTheCreditCard()。
Oops,消費(fèi)者被扣了五次錢。為什么?因?yàn)槟阆嘈诺谌綆?kù)只會(huì)調(diào)用你的回調(diào)一次。
所以你不得不被丟雞蛋并且給消費(fèi)者道歉歸還多扣的四次錢。然后你立刻采取措施確保這種情況不會(huì)再發(fā)生。你會(huì)怎么做呢?
你可能會(huì)創(chuàng)建一些狀態(tài)值來跟蹤你的回調(diào),當(dāng)它被調(diào)用一次之后會(huì)被標(biāo)記,然后就可以忽略任何意外的重復(fù)調(diào)用。無論第三方如何道歉并且承諾他們的bug已經(jīng)修復(fù)了,你再也不會(huì)相信他們了,不是嗎?
這看起來像一個(gè)愚蠢的場(chǎng)景,但是這可能比你想得還普遍。我們的程序變得越復(fù)雜,我們就會(huì)集成越多的第三方/外部代碼,這種愚蠢的場(chǎng)景就越容易發(fā)生。
布基膠帶你給你的回調(diào)加入了狀態(tài)跟蹤機(jī)制,然后睡了一個(gè)好覺。但是實(shí)際上你只是處理了信任列表許多項(xiàng)目中的一項(xiàng)。當(dāng)另一個(gè)bug造成另一個(gè)可靠性丟失的情況時(shí)會(huì)發(fā)生什么?更多的改造,更多丑陋的代碼。
更多布基膠帶。你必須不斷修復(fù)回調(diào)中的漏洞。無論你是多優(yōu)秀的開發(fā)者,無論你的布基膠帶多漂亮,事實(shí)就是:在你信任墻上的回調(diào)充滿了漏洞。
Promise解決方案一些人喜歡使用布基繃帶并且給信任墻上的洞打補(bǔ)丁。但是在某些時(shí)候,你也許會(huì)問自己,是否有其他模式來表達(dá)異步流程控制,不需要忍受所有這些可靠性丟失?
是的!Promises就是一個(gè)方法。
在我解釋它們是怎么工作之前,讓我來解釋一些它們背后的概念問題。
快餐業(yè)務(wù)你走進(jìn)你最喜愛的快餐店,走到前臺(tái)要了一些美味的食物。收銀員告訴你一共7.53美元然后你把錢給她。她會(huì)給回你什么東西呢?
如果你足夠幸運(yùn),你要的食物已經(jīng)準(zhǔn)備好了。但是大多數(shù)情況下,你會(huì)拿到一個(gè)寫著序列號(hào)的小票,是吧?所以你站到一邊等待你的食物。
很快,你聽到廣播響起:“請(qǐng)317號(hào)取餐”。正好是你的號(hào)碼。你走到前臺(tái)用小票換來你的食物!謝天謝地,你不用忍受太長(zhǎng)的等待。
剛才發(fā)生的是一個(gè)對(duì)于Promises很好的比喻。你走到前臺(tái)開始一個(gè)業(yè)務(wù),但是這個(gè)業(yè)務(wù)不能馬上完成。所以,你得到一個(gè)在遲些時(shí)候完成業(yè)務(wù)(你的食物)的promise(小票)。一旦你的食物準(zhǔn)備就緒,你會(huì)得到通知然后你第一時(shí)間用你的promise(小票)換來了你想要的東西:食物。
換句話說,帶有序列號(hào)的小票就是對(duì)于一個(gè)未來結(jié)果的承諾。
完成事件想想上面調(diào)用someAsyncThing(..)的例子。如果你可以調(diào)用它然后訂閱一個(gè)事件,當(dāng)這個(gè)調(diào)用完成的時(shí)候你會(huì)得到通知而不是傳遞一個(gè)回調(diào)給它,這樣難道不會(huì)更好嗎?
例如,想象這樣的代碼:
var listener = someAsyncThing(..) ; listener.on("completion",function(data){ //keep going now ! }) ;
實(shí)際上,如果我們還可以監(jiān)聽調(diào)用失敗的事件那就更好了。
listener.on("failure",function(){ //Oops,What"s plan B? }) ;
現(xiàn)在,對(duì)于我們調(diào)用的每個(gè)函數(shù),我們能夠在函數(shù)成功執(zhí)行或者失敗的時(shí)候得到通知。換句話說,每個(gè)函數(shù)調(diào)用會(huì)是流程控制圖上的決策點(diǎn)。
Promise"事件"Promises就像是一個(gè)函數(shù)在說“我這有一個(gè)事件監(jiān)聽器,當(dāng)我完成或者失敗的時(shí)候會(huì)被通知到?!蔽覀兛纯此窃趺垂ぷ鞯模?/p>
function someAsyncThing(){ var p = new Promise(function(resolve,reject){ //at some later time,call "resolve()" or "reject()" }) ; return p ; } var p = someAsyncThing() ; p.then( function(){ //success happened }, function(){ //failure happened } ) ;
你只需要監(jiān)聽then事件,然后通過知道哪個(gè)回調(diào)函數(shù)被調(diào)用就可以知道是成功還是失敗。
逆轉(zhuǎn)通過promises,我們重新獲得了程序的控制權(quán)而不是通過給第三方庫(kù)傳遞回調(diào)來轉(zhuǎn)移控制權(quán)。這是javascript中異步控制流程表達(dá)上一個(gè)很大的進(jìn)步。
“等等”,你說?!拔胰匀灰獋鬟f回調(diào)啊。有什么不一樣?!”嗯。。。好眼力!
有些人聲稱Promises通過移除回調(diào)來解決“回調(diào)地獄”的問題。并不是這樣!在一些情況下,你甚至需要比以前更多的回調(diào)。同時(shí),根據(jù)你如何編寫你的代碼,你可能仍然需要把promises嵌套在別的promises中!
批判性地看,promises所做的只是改變了你傳遞回調(diào)的地方。
本質(zhì)上,如果你把你的回調(diào)傳遞給擁有良好保證和可預(yù)測(cè)性的中立Promises機(jī)制,你實(shí)質(zhì)上重新獲得了對(duì)于后續(xù)程序能很穩(wěn)定并且運(yùn)行良好的可靠性。標(biāo)準(zhǔn)的promises機(jī)制有以下這些保證:
如果promise被resolve,它要不是success就是failure,不可能同時(shí)存在。
一旦promise被resolve,它就再也不會(huì)被resolve(不會(huì)出現(xiàn)重復(fù)調(diào)用)。
如果promise返回了成功的信息,那么你綁定在成功事件上的回調(diào)會(huì)得到這個(gè)消息。
如果發(fā)生了錯(cuò)誤,promise會(huì)收到一個(gè)帶有錯(cuò)誤信息的錯(cuò)誤通知。
無論promise最后的結(jié)果是什么(success或者failure),他就不會(huì)改變了,你總是可以獲得這個(gè)消息只要你不銷毀promise。
如果我們從someAsyncThing(..)得到的promise不是可用的標(biāo)準(zhǔn)的promise會(huì)發(fā)生什么?如果我們無法判斷我們是否可相信它是真的promise會(huì)怎么樣?
簡(jiǎn)單!只要你得到的是“類promise”的,也就是擁有then(..)方法可以注冊(cè)success和failure事件,那么你就可用使用這個(gè)“類promise”然后把它包裝在一個(gè)你信任的promise中。
var notSureWhatItIs = someAsyncThing(); var p = Promise.resolve( notSureWhatItIs ); // now we can trust `p`!! p.then( function(){ // success happened }, function(){ // failure happened } );
promises的最重要的特點(diǎn)就是它把我們處理任何函數(shù)調(diào)用的成功或者失敗的方式規(guī)范成了可預(yù)測(cè)的形式,特別是如果這個(gè)調(diào)用實(shí)際上的異步的。
在這個(gè)規(guī)范過程中,它使我們的程序在可控制的位置而不是把控制權(quán)交給一個(gè)不可相信的第三方。
總結(jié)不要管你所聽到的,“回調(diào)地獄”不是真的關(guān)于函數(shù)嵌套和它們?cè)诖a編輯器中產(chǎn)生的縮進(jìn)。它是關(guān)于控制轉(zhuǎn)移的,是指我們由于把控制權(quán)交給一個(gè)我們不能信任的第三方而產(chǎn)生的對(duì)我們的程序失去控制的現(xiàn)象。
Promises逆轉(zhuǎn)了這個(gè)情況,它使得我們重新獲得控制權(quán)。相比傳遞回調(diào)給第三方函數(shù),函數(shù)返回一個(gè)promise對(duì)象,我們可以使用它來監(jiān)聽函數(shù)的成功或失敗。在promise我們?nèi)匀皇褂没卣{(diào),但是重要的是標(biāo)準(zhǔn)的promise機(jī)制使我們可以信任它們行為的正確性。我們不需要想辦法來處理這些可靠性問題。
在第三部分:可靠性問題中,我會(huì)說道一個(gè)promises可靠性機(jī)制中很特別的部分:一個(gè)promise的狀態(tài)必須是可靠并且不可變的。
深入理解Promise五部曲--1.異步問題
深入理解Promise五部曲--2.轉(zhuǎn)換問題
深入理解Promise五部曲--3.可靠性問題
深入理解Promise五部曲--4.擴(kuò)展性問題
深入理解Promise五部曲--5.樂高問題
最后,安利下我的個(gè)人博客,歡迎訪問:http://bin-playground.top
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/87569.html
摘要:當(dāng)引擎開始執(zhí)行一個(gè)函數(shù)比如回調(diào)函數(shù)時(shí),它就會(huì)把這個(gè)函數(shù)執(zhí)行完,也就是說只有執(zhí)行完這段代碼才會(huì)繼續(xù)執(zhí)行后面的代碼。當(dāng)條件允許時(shí),回調(diào)函數(shù)就會(huì)被運(yùn)行?,F(xiàn)在,返回去執(zhí)行注冊(cè)的那個(gè)回調(diào)函數(shù)。 原文地址:http://blog.getify.com/promis... 在微博上看到有人分享LabJS作者寫的關(guān)于Promise的博客,看了下覺得寫得很好,分五個(gè)部分講解了Promise的來龍去脈。從...
摘要:有一個(gè)和相關(guān)的更大的問題。最后,請(qǐng)負(fù)有責(zé)任感并且使用安全的擴(kuò)展。深入理解五部曲異步問題深入理解五部曲轉(zhuǎn)換問題深入理解五部曲可靠性問題深入理解五部曲擴(kuò)展性問題深入理解五部曲樂高問題最后,安利下我的個(gè)人博客,歡迎訪問 原文地址:http://blog.getify.com/promis... 現(xiàn)在,我希望你已經(jīng)看過深入理解Promise的前三篇文章了。并且假設(shè)你已經(jīng)完全理解Promises...
摘要:簡(jiǎn)單的說,即將到來的標(biāo)準(zhǔn)指出是一個(gè),所以作為一個(gè),必須可以被子類化。保護(hù)還是子類化這是個(gè)問題我真的希望我能創(chuàng)建一個(gè)忠實(shí)的給及以下。 原文地址:http://blog.getify.com/promis... 如果你需要趕上我們關(guān)于Promise的進(jìn)度,可以看看這個(gè)系列前兩篇文章深入理解Promise五部曲--1.異步問題和深入理解Promise五部曲--2.控制權(quán)轉(zhuǎn)移問題。 Promi...
摘要:一個(gè)就像一個(gè)樂高玩具。問題是不是你小時(shí)候玩兒的那個(gè)有趣,它們不是充滿想象力的打氣筒,也不是一種樂高玩具。這是對(duì)的并不是給開發(fā)者使用的,它們是給庫(kù)作者使用的。不會(huì)超過這兩種情況。第二個(gè)是根據(jù)第一個(gè)處理函數(shù)如何運(yùn)行來自動(dòng)變成狀態(tài)成功或者失敗。 原文地址:http://blog.getify.com/promis... 在 Part4:擴(kuò)展問題 中,我討論了如何擴(kuò)展和抽象Promise是多么...
摘要:只要在調(diào)用異步函數(shù)時(shí)設(shè)置一個(gè)或多個(gè)回調(diào)函數(shù),函數(shù)就會(huì)在完成時(shí)自動(dòng)調(diào)用回調(diào)函數(shù)。要解決的問題是,如何將回調(diào)方法的參數(shù)從回調(diào)方法中傳遞出來,讓它可以像同步函數(shù)的返回結(jié)果一樣,在回調(diào)函數(shù)以外的控制范圍內(nèi),可以傳遞和復(fù)用。 摘要: 我們知道 JavaScript 自從有了 Generator 之后,就有了各種基于 Generator 封裝的協(xié)程。其中 hprose 中封裝的 Promise 和...
閱讀 1772·2021-10-11 10:59
閱讀 2416·2021-09-30 09:53
閱讀 1780·2021-09-22 15:28
閱讀 2804·2019-08-29 15:29
閱讀 1568·2019-08-29 13:53
閱讀 3217·2019-08-29 12:34
閱讀 2865·2019-08-26 10:16
閱讀 2673·2019-08-23 15:16