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)事宜。