??【異步】3. 異步方案之回調(diào)的不完美

異步進(jìn)化史

異步在實(shí)現(xiàn)上愚臀,依賴一些特殊的語法規(guī)則忆蚀。從整體上來說,異步方案經(jīng)歷了如下的四個(gè)進(jìn)化階段:

回調(diào)函數(shù) —> Promise —> Generator —> async/await

其中 Promise姑裂、Generator 和 async/await 都是在 ES2015 之后馋袜,慢慢發(fā)展起來的、具有一定顛覆性的新異步方案舶斧。相較于 “回調(diào)函數(shù) “時(shí)期的刀耕火種而言欣鳖,具有劃時(shí)代的意義。

“回調(diào)函數(shù)”時(shí)期存在的問題

  1. 回調(diào)嵌套 -> 理解問題茴厉,缺乏順序性
    場景:根據(jù)第一個(gè)網(wǎng)絡(luò)請求的結(jié)果泽台,再去執(zhí)行第二個(gè)網(wǎng)絡(luò)請求;然后根據(jù)第二個(gè)網(wǎng)絡(luò)請求的結(jié)果執(zhí)行第三個(gè)網(wǎng)絡(luò)請求... 于是出現(xiàn)了如下的代碼矾缓,臭名昭著的“回調(diào)地獄”現(xiàn)身怀酷。
請求1(function(請求結(jié)果1){
    請求2(function(請求結(jié)果2){
        請求3(function(請求結(jié)果3){
            請求4(function(請求結(jié)果4){
                請求5(function(請求結(jié)果5){
                    請求6(function(請求結(jié)果3){
                        ...
                    })
                })
            })
        })
    })
})

這種嵌套的書寫方式,排查問題時(shí)我們需要繞過很多障眼法嗜闻,不斷的在函數(shù)間跳轉(zhuǎn)蜕依,甚至需要花費(fèi)一些時(shí)間去思考真正的執(zhí)行順序。嵌套和縮進(jìn)只是回調(diào)地獄的一個(gè)梗琉雳,它導(dǎo)致的問題遠(yuǎn)不止嵌套導(dǎo)致的可讀性降低样眠。
大腦對于事情的計(jì)劃方式時(shí)線性的、阻塞的翠肘、單線程的語義檐束,但是回調(diào)表達(dá)異步流程的方式是非線性的、非順序的锯茄,這使得正確推導(dǎo)這樣的代碼難度很大厢塘。難以理解的代碼是壞代碼茶没,會(huì)導(dǎo)致壞bug。 回調(diào)地獄帶來的負(fù)作用有以下幾點(diǎn):

  • 代碼臃腫
  • 可讀性差
  • 耦合度過高晚碾,可維護(hù)性差
  • 代碼復(fù)用性差
  • 容易滋生bug
  • 只能再回調(diào)里處理異常

我們需要一種更同步抓半、更順序、更阻塞的方式來表達(dá)異步格嘁,就像我們的大腦一樣笛求。

  1. 控制反轉(zhuǎn) -> 信任問題
    A和B發(fā)生于現(xiàn)在,在JavaScript主程序的直接控制之下糕簿,而C會(huì)延遲到將來發(fā)生探入,并且是在第三方的控制下(多數(shù)情況下,是某個(gè)第三方提供的工具)懂诗。這種控制的轉(zhuǎn)移通常不會(huì)給程序帶來很多問題蜂嗽。
    我們用回調(diào)函數(shù)來封裝程序中的continuation,然后把回調(diào)交給第三方(甚至可能是外部代碼)殃恒,接著期待其能夠調(diào)用回調(diào)植旧,實(shí)現(xiàn)正確的功能。這種稱為 控制反轉(zhuǎn) 离唐,就是把自己程序一部分的執(zhí)行控制交給某個(gè)第三方病附。第三方提供某個(gè)工具,你傳入回調(diào)處理自己的邏輯亥鬓,由于你的代碼和第三方工具之間沒有一份明確表達(dá)的契約完沪,他們調(diào)用你的回調(diào)時(shí)可能出現(xiàn)一些情況:
  • 調(diào)用回調(diào)過早
  • 調(diào)用回調(diào)過晚(或者沒有調(diào)用)
  • 調(diào)用回調(diào)的次數(shù)太少或太多
  • 沒有把所需的環(huán)境/參數(shù)成功傳給你的回調(diào)函數(shù)
  • 吞掉可能出現(xiàn)的錯(cuò)誤或異常
// A
ajax("..", function(){
  // C
});
// B

對于被傳給你無法信任的工具的每個(gè)問題,你都將不得不創(chuàng)建大量的混亂邏輯嵌戈,此時(shí)是否更加明白回調(diào)地獄是多像地獄了吧覆积!
回調(diào)最大的問題是控制反轉(zhuǎn),它會(huì)導(dǎo)致信任鏈的完全斷裂咕别!

回調(diào)的變體

回調(diào)設(shè)計(jì)存在幾個(gè)變體技健,意在解決前面討論的一些信任問題(不是全部P囱ā)

分離回調(diào)

為了更優(yōu)雅地處理錯(cuò)誤惰拱,有些API設(shè)計(jì)提供了 分離回調(diào)(一個(gè)用于成功通知,一個(gè)用于出錯(cuò)通知)

function success(data){
  console.log(data);
}
function failure(err){
  console.error(err);
}
ajax("http://some.url.1", success, failure);

這種設(shè)計(jì)啊送,API的出錯(cuò)處理函數(shù) failure() 常常是可選的偿短,如果沒有提供的話,就是假定這個(gè)錯(cuò)誤可以吞掉馋没。ES6 Promise API使用的就是這種分離回調(diào)設(shè)計(jì)昔逗。

error-first

error-first風(fēng)格 回調(diào)模式,也稱Node風(fēng)格篷朵,因?yàn)閹缀跛蠳ode.js API都采用這種風(fēng)格勾怒。
其中回調(diào)的第一個(gè)參數(shù)保留用作錯(cuò)誤對象婆排。如果成功的話,這個(gè)參數(shù)就會(huì)被清空/置假(后續(xù)的參數(shù)就是成功數(shù)據(jù))笔链。如果產(chǎn)生了錯(cuò)誤結(jié)果段只,第一個(gè)參數(shù)就會(huì)被置起/置真(通常就不會(huì)再傳遞其他結(jié)果)。

function response(err, data){
  if(err){
    console.error(err)
  }else{
    console.log(data)
  }
}
ajax("http://some.url.1", response);

存在問題

這并沒有像表面看上去那樣真正解決主要的信任問題鉴扫,并沒有涉及阻止或過濾不想要的重復(fù)調(diào)用回調(diào)的問題≡拚恚現(xiàn)在事情更糟糕,因?yàn)楝F(xiàn)在你可能同時(shí)得到成功或失敗的結(jié)果坪创,或者都沒有炕婶,并且你還不得不編碼處理這些情況。
盡管這是可采用的標(biāo)準(zhǔn)模式莱预,但更加冗長和模式化柠掂,可復(fù)用性不高,還得給應(yīng)用中的每個(gè)回調(diào)添加這樣的代碼依沮。
??? 如何解決完全不調(diào)用的信任問題陪踩?設(shè)置一個(gè)超時(shí)來取消事件
??? 如何解決調(diào)用過早的信任問題?永遠(yuǎn)要異步悉抵,創(chuàng)建一個(gè)類似于驗(yàn)證概念版本的asyncify()工具

雖然可以寫一些特點(diǎn)邏輯來解決這些信任問題肩狂,但其難度高于應(yīng)有的水平,可能會(huì)產(chǎn)生更笨重姥饰、更難維護(hù)的代碼傻谁,并且缺少足夠的保護(hù),其中的損害要直到你受到bug的影響才會(huì)被發(fā)現(xiàn)列粪。
我們需要一個(gè)通用的方案來解決這些信任問題审磁。不管我們創(chuàng)建多少回調(diào),這一方案都應(yīng)可以復(fù)用岂座,且沒有重復(fù)代碼的開銷态蒂。

異常處理

try…catch是同步代碼,只能捕獲“同步代碼”中的"運(yùn)行時(shí)異常"费什,"同步代碼"是無法獲取如setTimeout钾恢、Promise等異步代碼的異常。

Q:為什么 try...catch 無法直接捕獲異步的錯(cuò)誤鸳址?
比如執(zhí)行 fs.readdir 的時(shí)候瘩蚪,其實(shí)是將回調(diào)函數(shù)加入任務(wù)隊(duì)列中,代碼繼續(xù)執(zhí)行稿黍,直至主線程完成后疹瘦,才會(huì)從任務(wù)隊(duì)列中選擇已完成的任務(wù),并將其加入棧中巡球,此時(shí)棧中只有這一個(gè)執(zhí)行上下文言沐,如果回調(diào)報(bào)錯(cuò)邓嘹,也無法獲取調(diào)用該異步操作時(shí)的棧中的信息,不容易判定哪里出現(xiàn)了錯(cuò)誤险胰。

因此吴超,要處理 setTimeout 等回調(diào)內(nèi)部的異常,只能將 try-catch 放置到回調(diào)內(nèi)部鸯乃。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鲸阻,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子缨睡,更是在濱河造成了極大的恐慌鸟悴,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件奖年,死亡現(xiàn)場離奇詭異细诸,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)陋守,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門震贵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人水评,你說我怎么就攤上這事猩系。” “怎么了中燥?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵寇甸,是天一觀的道長。 經(jīng)常有香客問我疗涉,道長拿霉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任咱扣,我火速辦了婚禮绽淘,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘闹伪。我一直安慰自己沪铭,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布祭往。 她就那樣靜靜地躺著伦意,像睡著了一般火窒。 火紅的嫁衣襯著肌膚如雪硼补。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天熏矿,我揣著相機(jī)與錄音,去河邊找鬼难礼。 笑死浸船,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的卵渴。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼鲤竹,長吁一口氣:“原來是場噩夢啊……” “哼浪读!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起辛藻,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤碘橘,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后吱肌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體痘拆,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年氮墨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了纺蛆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡规揪,死狀恐怖桥氏,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情猛铅,我是刑警寧澤识颊,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站奕坟,受9級特大地震影響祥款,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜月杉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一刃跛、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧苛萎,春花似錦桨昙、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至翘盖,卻和暖如春桂塞,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背馍驯。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工阁危, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留玛痊,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓狂打,卻偏偏與公主長得像擂煞,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子趴乡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355