如何正確合理使用 JavaScript async/await

ES7 引入的?async/await?在 JavaScript 的異步編程中是一個極好的改進狂秦。它提供了使用同步樣式代碼異步訪問?resoruces的方式,而不會阻塞主線程逗余。然而,它們也存在一些坑及問題泽腮。在本文中,將從不同的角度探討?async/await衣赶,并演示如何正確有效地使用這對兄弟诊赊。

前置知識

async 作用是什么

從?MDN?可以看出:

async?函數(shù)返回的是一個?Promise?對象。async?函數(shù)(包含函數(shù)語句府瞄、函數(shù)表達式碧磅、Lambda表達式)會返回一個?Promise?對象,如果在函數(shù)中?return?一個直接量遵馆,async?會把這個直接量通過?Promise.resolve()?封裝成?Promise?對象鲸郊。

如果async函數(shù)沒有返回值, 它會返回Promise.resolve(undefined)货邓。

await 作用是什么

從?MDN?了解到:

await等待的是一個表達式秆撮,這個表達式的計算結(jié)果是Promise對象或者其它值(換句話說,await可以等任意表達式的結(jié)果)逻恐。

如果它等到的不是一個 Promise 對象像吻,那 await 表達式的運算結(jié)果就是它等到的東西。

如果它等到的是一個 Promise 對象复隆,await 就忙起來了,它會阻塞后面的代碼姆涩,等著 Promise 對象 resolve挽拂,然后得到 resolve 的值,作為 await 表達式的運算結(jié)果骨饿。

這就是 await 必須用在 async 函數(shù)中的原因亏栈。async 函數(shù)調(diào)用不會造成阻塞,它內(nèi)部所有的阻塞都被封裝在一個 Promise 對象中異步執(zhí)行宏赘。

async/await?的優(yōu)點

async/await帶給我們的最重要的好處是同步編程風(fēng)格绒北。

讓我們看一個例子:

很明顯,async/await版本比promise版本更容易理解察署。如果忽略await關(guān)鍵字闷游,代碼看起來就像任何其他同步語言,比如Python贴汪。

最佳的地方不僅在于可讀性脐往。async/await到今天為止,所有主流瀏覽器都完全支持異步功能扳埂。

本地瀏覽器的支持意味著你不必轉(zhuǎn)換代碼业簿。更重要的是,它便于調(diào)試阳懂。當(dāng)在函數(shù)入口點設(shè)置斷點并跨過

await?行時梅尤,將看到調(diào)試器在?bookModel.fetchAll()?執(zhí)行其任務(wù)時暫停片刻柜思,然后它將移動到下一個.filter?行,這比?promise?代碼要簡單得多,在?promise?中巷燥,必須在?.filter?行上設(shè)置另一個斷點酝蜒。

另一個不太明顯的優(yōu)點是?async?關(guān)鍵字。?async聲明?getBooksByAuthorWithAwait()函數(shù)返回值確保是一個?promise矾湃,因此調(diào)用者可以安全地使用?getBooksByAuthorWithAwait().then(...)?或await?getBooksByAuthorWithAwait()亡脑。 想想下面的例子(不好的做法!):

在上述代碼中邀跃,getBooksByAuthorWithPromise?可能返回?promise(正常情況下)或?null?值(異常情況下)霉咨,在異常情況下,調(diào)用者不能調(diào)用?.then()拍屑。有了async?聲明途戒,這種情況就不會出現(xiàn)了。

順便給大家推薦一個裙僵驰,它的前面是 537喷斋,中間是631,最后就是 707蒜茴。想要學(xué)習(xí)前端的小伙伴可以加入我們一起學(xué)習(xí)星爪,互相幫助。群里每天晚上都有大神免費直播上課粉私,如果不是想學(xué)習(xí)的小伙伴就不要加啦顽腾。

async/await?可能會產(chǎn)生誤導(dǎo)

一些文章將async/wait?與?Promise?進行了比較,并聲稱它是 JavaScript 下一代異步編程風(fēng)格诺核,對此作者深表異議抄肖。async/await?是一種改進,但它只不過是一種語法糖窖杀,不會完全改變我們的編程風(fēng)格漓摩。

從本質(zhì)上說,async?函數(shù)仍然是?promise入客。在正確使用?async?函數(shù)之前管毙,你必須先了解?promise,更糟糕的是痊项,大多數(shù)時候你需要在使用?promises?的同時使用?async?函數(shù)锅风。

考慮上面示例中的?getBooksByAuthorWithAwait()?和getbooksbyauthorwithpromise()?函數(shù)。請注意鞍泉,它們不僅功能相同皱埠,而且具有完全相同的接口!

這意味著?getbooksbyauthorwithwait()?將返回一個?promise,所以也可以使用?.then(...)方式來調(diào)用它咖驮。

嗯边器,這未必是件壞事训枢。只有?await?的名字給人一種感覺,“哦忘巧,太好了恒界,可以把異步函數(shù)轉(zhuǎn)換成同步函數(shù)了”,這實際上是錯誤的砚嘴。

async/await

那么在使用?async/await?時可能會犯什么錯誤呢?下面是一些常見的例子十酣。

太過串行化

盡管await可以使代碼看起來像是同步的,但實際它們?nèi)匀皇钱惒降募食ぃ仨毿⌒谋苊馓^串行化耸采。

上述代碼在邏輯上看似正確的,然而工育,這是錯誤的虾宇。

1.await bookModel.fetchAll()?會等待?fetchAll()?直到?fetchAll()?返回結(jié)果。

2.然后?await authorModel.fetch(authorId)?被調(diào)用如绸。

順便給大家推薦一個裙嘱朽,它的前面是 537,中間是631怔接,最后就是 707搪泳。想要學(xué)習(xí)前端的小伙伴可以加入我們一起學(xué)習(xí),互相幫助蜕提。群里每天晚上都有大神免費直播上課森书,如果不是想學(xué)習(xí)的小伙伴就不要加啦。

注意谎势,authorModel.fetch(authorId)并不依賴于bookModel.fetchAll()的結(jié)果,實際上它們可以并行調(diào)用杨名!然而脏榆,用了 await,兩個調(diào)用變成串行的台谍,總的執(zhí)行時間將比并行版本要長得多得多须喂。

下面是正確的方式:

更糟糕的是,如果你想要一個接一個地獲取項目列表趁蕊,你必須依賴使用promises:

簡而言之坞生,你仍然需要將流程視為異步的,然后使用await寫出同步的代碼掷伙。在復(fù)雜的流程中是己,直接使用promise可能更方便。

錯誤處理

promise中任柜,異步函數(shù)有兩個可能的返回值:resolved和rejected卒废。我們可以用.then()處理正常情況沛厨,用.catch()處理異常情況。然而摔认,使用async/await方式的逆皮,錯誤處理可能比較棘手。

try…catch

最標(biāo)準(zhǔn)的(也是作者推薦的)方法是使用try...catch語法参袱。在await調(diào)用時电谣,在調(diào)用await函數(shù)時,如果出現(xiàn)非正常狀況就會拋出異常,await命令后面的promise對象抹蚀,運行結(jié)果可能是rejected剿牺,所以最好把await命令放在try...catch代碼塊中。如下例子:

在捕捉到異常之后况鸣,有幾種方法來處理它:

處理異常牢贸,并返回一個正常值。(不在?catch?塊中使用任何?return?語句相當(dāng)于使用?return undefined镐捧,undefined 也是一個正常值潜索。)

如果你想讓調(diào)用者處理它,你可以直接拋出普通的錯誤對象懂酱,如?throw errorr竹习,它允許你在promise鏈中使用async getBooksByAuthorWithAwait()?函數(shù)(也就是說,可以像getBooksByAuthorWithAwait().then(...).catch(error => ...) 處理錯誤); 或者可以用?Error?對象將錯誤封裝起來列牺,如?throw new Error(error)整陌,當(dāng)這個錯誤在控制臺中顯示時,它將給出完整的堆棧跟蹤信息瞎领。

拒絕它泌辫,就像?return Promise.reject(error)?,這相當(dāng)于?throw error九默,所以不建議這樣做震放。

使用try...catch的好處:

簡單,傳統(tǒng)。只要有Java或c++等其他語言的經(jīng)驗驼修,理解這一點就不會有任何困難殿遂。

如果不需要每步執(zhí)行錯誤處理,你仍然可以在一個?try ... catch?塊中包裝多個?await?調(diào)用來處理一個地方的錯誤乙各。

順便給大家推薦一個裙墨礁,它的前面是 537,中間是631耳峦,最后就是 707恩静。想要學(xué)習(xí)前端的小伙伴可以加入我們一起學(xué)習(xí),互相幫助妇萄。群里每天晚上都有大神免費直播上課蜕企,如果不是想學(xué)習(xí)的小伙伴就不要加啦咬荷。

這種方法也有一個缺陷。由于try...catch會捕獲代碼塊中的每個異常轻掩,所以通常不會被 promise 捕獲的異常也會被捕獲到幸乒。比如:

運行此代碼,你將得到一個錯誤ReferenceError: cb is not defined唇牧。這個錯誤是由console.log()打印出來的的罕扎,而不是 JavaScript 本身。有時這可能是致命的:如果BookModel被包含在一系列函數(shù)調(diào)用中丐重,其中一個調(diào)用者吞噬了錯誤腔召,那么就很難找到這樣一個未定義的錯誤。

讓函數(shù)返回兩個值

另一種錯誤處理方法是受到Go語言的啟發(fā)扮惦。它允許異步函數(shù)返回錯誤和結(jié)果臀蛛。詳情請看這篇博客文章:

How to write async await without try-catch blocks in Javascript:https://blog.grossman.io/how-to-write-async-await-without-try-catch-blocks-in-javascript/

簡而言之,你可以像這樣使用異步函數(shù):

[err, user] = await to(UserModel.findById(1));

作者個人不喜歡這種方法崖蜜,因為它將 Go 語言的風(fēng)格帶入到了 JavaScript 中浊仆,感覺不自然。但在某些情況下豫领,這可能相當(dāng)有用抡柿。

使用 .catch

這里介紹的最后一種方法就是繼續(xù)使用.catch()。

回想一下await的功能:它將等待promise完成它的工作等恐。值得注意的一點是promise.catch()也會返回一個promise洲劣,所以我們可以這樣處理錯誤:

這種方法有兩個小問題:

它是 promises 和 async 函數(shù)的混合體。你仍然需要理解 是promises 如何工作的课蔬。

錯誤處理先于正常路徑囱稽,這是不直觀的。

結(jié)論

ES7引入的async/await關(guān)鍵字無疑是對J avaScrip t異步編程的改進二跋。它可以使代碼更容易閱讀和調(diào)試粗悯。然而,為了正確地使用它們同欠,必須完全理解promise,因為async/await只不過是promise的語法糖横缔,本質(zhì)上仍然是promise铺遂。

*? 作者:Charlee?Li

*? 翻譯:前端小智

*? 原文地址:https://segmentfault.com/a/1190000017718513

*? 聲明:轉(zhuǎn)載文章和圖片均來自公開網(wǎng)絡(luò),版權(quán)歸作者本人所有茎刚。如果出處有誤或侵犯到原作者權(quán)益襟锐,請與我們聯(lián)系刪除或授權(quán)事宜。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末膛锭,一起剝皮案震驚了整個濱河市粮坞,隨后出現(xiàn)的幾起案子蚊荣,更是在濱河造成了極大的恐慌,老刑警劉巖莫杈,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件互例,死亡現(xiàn)場離奇詭異,居然都是意外死亡筝闹,警方通過查閱死者的電腦和手機媳叨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來关顷,“玉大人糊秆,你說我怎么就攤上這事∫樗” “怎么了痘番?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長平痰。 經(jīng)常有香客問我汞舱,道長,這世上最難降的妖魔是什么觉增? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任兵拢,我火速辦了婚禮,結(jié)果婚禮上逾礁,老公的妹妹穿的比我還像新娘说铃。我一直安慰自己,他們只是感情好嘹履,可當(dāng)我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布腻扇。 她就那樣靜靜地躺著,像睡著了一般砾嫉。 火紅的嫁衣襯著肌膚如雪幼苛。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天焕刮,我揣著相機與錄音舶沿,去河邊找鬼。 笑死配并,一個胖子當(dāng)著我的面吹牛括荡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播溉旋,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼畸冲,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起邑闲,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤算行,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后苫耸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體州邢,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年鲸阔,在試婚紗的時候發(fā)現(xiàn)自己被綠了偷霉。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡褐筛,死狀恐怖类少,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情渔扎,我是刑警寧澤硫狞,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站晃痴,受9級特大地震影響残吩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜倘核,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一泣侮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧紧唱,春花似錦活尊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至绰疤,卻和暖如春铜犬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背轻庆。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工癣猾, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人余爆。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓煎谍,卻偏偏與公主長得像,于是被迫代替她去往敵國和親龙屉。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,486評論 2 348

推薦閱讀更多精彩內(nèi)容