[譯]JavaScript async / await:好處、坑和正確用法

原文地址:JavaScript async/await: The Good Part, Pitfalls and How to Usejavascript

ES7經過介紹async/await使得JavaScript的異步編程實現了重大改進。它提供了一種使用同步代碼樣式異步訪問resoruces的方式,並且不會阻塞主線程。然而,使用它有點棘手。在本文中,咱們將從不一樣的角度探討async/await,並將展現如何正確有效地使用它們。java

async/await的好處

async/await給咱們帶來的最重要的好處是同步編程風格。咱們來看一個例子吧。編程

很明顯, async/await版本比 promise版本更容易理解。若是忽略 await關鍵字,代碼看起來就像任何其餘同步語言,如Python。

好的一面不只是可讀性,async/await有本地瀏覽器支持。截至今天,全部主流瀏覽器 查看都徹底支持異步功能。promise

本機支持意味着您沒必要轉換代碼。更重要的是,它有助於調試。當您在函數入口設置斷點並跳過await行時,您將看到調試器在bookModel.fetchAll()執行期間暫停一段時間,而後移動到下一 行.filter!這比promise狀況要容易得多,在promise狀況下你必須在.filter行設置另外一個斷點 。瀏覽器

另外一個不太明顯的好處是 async關鍵字。它聲明 getBooksByAuthorWithAwait()函數返回值確保是一個 promise,以便調用者能夠安全調用 getBooksByAuthorWithAwait().then(...)await getBooksByAuthorWithAwait()。看看下面的代碼(很差的作法!):

在上面的代碼中, getBooksByAuthorWithPromise能夠返回一個 promise(正常狀況)或一個 null值(例外狀況),在這種狀況下,調用者不能安全地調用 .then() 。經過 async聲明,這種返回 null的狀況將不可能出現。

Async/await可能會產生誤導

有些文章將async/awaitPromise進行比較,並聲稱它是JavaScript異步編程演變的下一代,我表示不一樣意。Async/await是一種改進,但它只不過是一種語法糖,它不會徹底改變咱們的編程風格。安全

從本質上講,await函數仍然是promise。在正確使用await函數以前,您必須瞭解promises,還有就是,大多數狀況下您須要同時使用promises和異步函數。異步

考慮上面示例中的getBooksByAuthorWithAwait()getBooksByAuthorWithPromises()函數。請注意,它們不只在功能上相同,並且具備徹底相同的接口。async

若是直接調用getBooksByAuthorWithAwait(),這意味着將返回一個promise異步編程

嗯,這並非壞事。只是await這個名稱讓人感受「哦,這能夠將異步函數轉換爲同步函數」,這其實是錯誤的。函數

Async/await的坑

那麼使用async/await時會出現什麼錯誤?如下一些常見的狀況。

太順序了

雖然await可使您的代碼看起來像同步,但請記住它們仍然是異步的,必須注意避免過於順序。

此代碼看起來邏輯正確。但這是錯誤的。

  1. await bookModel.fetchAll()將等到fetchAll()返回。
  2. 而後await authorModel.fetch(authorId)將被調用。 請注意authorModel.fetch(authorId)它不依賴於bookModel.fetchAll()的結果,實際上它們能夠並行調用!可是,經過await在這裏使用,這兩個調用變爲順序,而且總執行時間將比並行版本長得多。

這是正確的方法:

或者更糟糕的是,若是你想逐個獲取一個列表的項,必須依賴 promise
簡單地說,您仍然須要異步考慮工做流,而後嘗試同步編寫代碼 await。在複雜的工做流程中,直接使用 promises可能更容易。

錯誤處理

使用promises,異步函數有兩個可能的返回值:已解析的值和被拒絕的值。咱們能夠.then()用於正常狀況,.catch()用於特殊狀況。可是,async/await錯誤處理可能會很棘手。

try…catch

最標準的(我推薦的)方法是使用try...catch語句。當一個await調用時,任何被拒絕的值都將做爲異常拋出。這是一個例子:

catch的錯誤正是被拒絕的值。在咱們發現異常後,有幾種方法來處理它:

  1. 處理異常,並返回正常值。(不在catch塊中使用任何return語句,這等同於使用return undefined,也是正常值。)
  2. 拋出它,若是你想讓調用者處理它。能夠直接拋出普通的錯誤對象throw error,這在promise鏈中容許使用async getBooksByAuthorWithAwait()函數(即仍然能夠像這樣調用它getBooksByAuthorWithAwait().then(...).catch(error => ...)); 或者可使用Error對象包裝錯誤,例如throw new Error(error) ,當控制檯中顯示此錯誤時,將提供完整的堆棧跟蹤。
  3. 拒絕它,就像return Promise.reject(error) 。這至關於throw error不推薦。

使用try...catch的好處是

  • 簡單,傳統。只要您有Java或C++等其餘語言的經驗,就不會有任何困難。
  • 若是不須要每步執行錯誤處理,仍然能夠在一個try...catch塊中包裝多個await調用在一個位置處理錯誤。

這種方法也存在一個缺陷。因爲try...catch將捕獲塊中的每一個異常,所以將會捕獲一些一般不會被promises捕獲的異常。想一想這個例子:

運行此代碼,您將在控制檯中收到 ReferenceError: cb is not defined錯誤。錯誤是由 console.log()輸出而不是JavaScript自己。有時這多是致命的。若是 BookModel被深深地包含在一系列函數調用中,而且其中一個調用吞噬了錯誤,那麼找到這樣的未定義錯誤將很是困難。

使函數返回兩個值

另外一種錯誤處理方式受Go語言的啓發。它容許異步函數返回錯誤和結果。有關詳細信息,請參閱此博客文章: How to write async await without try-catch blocks in Javascript

簡言之,您可使用這樣的await函數:

我我的不喜歡這種方法,由於它將Go風格帶入JavaScript,感受不天然,但在某些狀況下,這可能很是有用。

使用.catch

咱們將在這裏介紹的最後一種方法是繼續使用 .catch()

回想一下await的功能:它將等待promise完成其工做。再回想一下,promise.catch()也將是一個promise,因此咱們能夠像這樣編寫錯誤處理:

這種方法有兩個小問題:

  1. 它是promisesawait函數的混合體。您仍然須要瞭解promises的工做原理。
  2. 錯誤處理在正常路徑以前進行,這樣不直觀。

結論

ES7引入的關鍵字async/await確定是對JavaScript異步編程的改進。它可使代碼更容易閱讀和調試。然而,爲了正確使用它們,必須徹底理解promise,由於它們只不過是語法糖,而潛在的技術仍然是promise

相關文章
相關標籤/搜索