原文地址:JavaScript async/await: The Good Part, Pitfalls and How to Use
ES7通過介紹async/await
使得JavaScript的非同步程式設計實現了重大改進。它提供了一種使用同步程式碼樣式非同步訪問resoruces
的方式,而且不會阻塞主執行緒。然而,使用它有點棘手。在本文中,我們將從不同的角度探討async/await
,並將展示如何正確有效地使用它們。
async/await的好處
async/await
給我們帶來的最重要的好處是同步程式設計風格。我們來看一個例子吧。
![[譯]JavaScript async / await:好處、坑和正確用法](https://i.iter01.com/images/ffd1241037b561c7e1cc6c2516bbb622d560b484f74b784d43ef4614aef43342.png)
async/await
版本比promise
版本更容易理解。如果忽略await
關鍵字,程式碼看起來就像任何其他同步語言,如Python。
好的一面不僅是可讀性,async/await
有本地瀏覽器支援。截至今天,所有主流瀏覽器 檢視都完全支援非同步功能。
![[譯]JavaScript async / await:好處、坑和正確用法](https://i.iter01.com/images/63c0ddf26fd7c48f765ddda30d23269ae404436366b9922cecc1d78f928a2d09.png)
本機支援意味著您不必轉換程式碼。更重要的是,它有助於除錯。當您在函式入口設定斷點並跳過await
行時,您將看到偵錯程式在bookModel.fetchAll()
執行期間暫停一段時間,然後移動到下一 行.filter
!這比promise
情況要容易得多,在promise
情況下你必須在.filter
行設定另一個斷點 。
![[譯]JavaScript async / await:好處、坑和正確用法](https://i.iter01.com/images/e1446b3ffda5939b5bf176fea9a55970ef3496d61c1f247467bb215cc9993639.gif)
async
關鍵字。它宣告getBooksByAuthorWithAwait()
函式返回值確保是一個promise
,以便呼叫者可以安全呼叫getBooksByAuthorWithAwait().then(...)
或await getBooksByAuthorWithAwait()
。看看下面的程式碼(不好的做法!):
![[譯]JavaScript async / await:好處、坑和正確用法](https://i.iter01.com/images/917c0a5b14cac0b128a2934ea93a8b8f1086456cbbbd64b864c362ed0be7efa1.png)
getBooksByAuthorWithPromise
可以返回一個promise
(正常情況)或一個null
值(例外情況),在這種情況下,呼叫者不能安全地呼叫.then()
。通過async
宣告,這種返回null
的情況將不可能出現。
Async/await可能會產生誤導
有些文章將async/await
與Promise
進行比較,並聲稱它是JavaScript非同步程式設計演變的下一代,我表示不同意。Async/await
是一種改進,但它只不過是一種語法糖,它不會完全改變我們的程式設計風格。
從本質上講,await
函式仍然是promise
。在正確使用await
函式之前,您必須瞭解promises
,還有就是,大多數情況下您需要同時使用promises
和非同步函式。
考慮上面示例中的getBooksByAuthorWithAwait()
和getBooksByAuthorWithPromises()
函式。請注意,它們不僅在功能上相同,而且具有完全相同的介面。
如果直接呼叫getBooksByAuthorWithAwait()
,這意味著將返回一個promise
。
嗯,這並不是壞事。只是await
這個名稱讓人感覺“哦,這可以將非同步函式轉換為同步函式”,這實際上是錯誤的。
Async/await的坑
那麼使用async/await
時會出現什麼錯誤?以下一些常見的情況。
太順序了
雖然await
可以使您的程式碼看起來像同步,但請記住它們仍然是非同步的,必須注意避免過於順序。
![[譯]JavaScript async / await:好處、坑和正確用法](https://i.iter01.com/images/8ab6bcb1d95e15e210881e8244f8996a7cf5ed0af45b2fb6daf0f0e7f3aec042.png)
await bookModel.fetchAll()
將等到fetchAll()
返回。- 然後
await authorModel.fetch(authorId)
將被呼叫。 請注意,authorModel.fetch(authorId)
它不依賴於bookModel.fetchAll()
的結果,實際上它們可以並行呼叫!但是,通過await
在這裡使用,這兩個呼叫變為順序,並且總執行時間將比並行版本長得多。
這是正確的方法:
![[譯]JavaScript async / await:好處、坑和正確用法](https://i.iter01.com/images/3619fb44743f4fa7a69eb48edc234bfede1d615088ee7ee348fc616068d44483.png)
promise
:
![[譯]JavaScript async / await:好處、坑和正確用法](https://i.iter01.com/images/ed052e79e51656bf8ee32a3cfb9bdac3a169aae0f0354389c224e24d194efded.png)
await
。在複雜的工作流程中,直接使用promises
可能更容易。
錯誤處理
使用promises
,非同步函式有兩個可能的返回值:已解析的值和被拒絕的值。我們可以.then()
用於正常情況,.catch()
用於特殊情況。但是,async/await
錯誤處理可能會很棘手。
try…catch
最標準的(我推薦的)方法是使用try...catch
語句。當一個await
呼叫時,任何被拒絕的值都將作為異常丟擲。這是一個例子:
![[譯]JavaScript async / await:好處、坑和正確用法](https://i.iter01.com/images/6edaf071a83597c2e416a94dbea732ca00cf627ddc5a88b3d501ea615f255665.png)
catch
的錯誤正是被拒絕的值。在我們發現異常後,有幾種方法來處理它:
- 處理異常,並返回正常值。(不在
catch
塊中使用任何return
語句,這等同於使用return undefined
,也是正常值。) - 丟擲它,如果你想讓呼叫者處理它。可以直接丟擲普通的錯誤物件
throw error
,這在promise
鏈中允許使用async getBooksByAuthorWithAwait()
函式(即仍然可以像這樣呼叫它getBooksByAuthorWithAwait().then(...).catch(error => ...)
); 或者可以使用Error
物件包裝錯誤,例如throw new Error(error)
,當控制檯中顯示此錯誤時,將提供完整的堆疊跟蹤。 - 拒絕它,就像
return Promise.reject(error)
。這相當於throw error
不推薦。
使用try...catch的好處是:
- 簡單,傳統。只要您有Java或C++等其他語言的經驗,就不會有任何困難。
- 如果不需要每步執行錯誤處理,仍然可以在一個
try...catch
塊中包裝多個await
呼叫在一個位置處理錯誤。
這種方法也存在一個缺陷。由於try...catch
將捕獲塊中的每個異常,因此將會捕獲一些通常不會被promises
捕獲的異常。想想這個例子:
![[譯]JavaScript async / await:好處、坑和正確用法](https://i.iter01.com/images/8511a2bb3eb9c8ec11aeee84258b2c19aff464d74079d48f9b14d46f2e80c9a4.png)
ReferenceError: cb is not defined
錯誤。錯誤是由console.log()
輸出而不是JavaScript本身。有時這可能是致命的。如果BookModel
被深深地包含在一系列函式呼叫中,並且其中一個呼叫吞噬了錯誤,那麼找到這樣的未定義錯誤將非常困難。
使函式返回兩個值
另一種錯誤處理方式受Go語言的啟發。它允許非同步函式返回錯誤和結果。有關詳細資訊,請參閱此部落格文章: How to write async await without try-catch blocks in Javascript
簡言之,您可以使用這樣的await
函式:
![[譯]JavaScript async / await:好處、坑和正確用法](https://i.iter01.com/images/94547aa551a2dae52b693fbfce7c3ea280acbb0a1984886e65d8064b432159fd.png)
使用.catch
我們將在這裡介紹的最後一種方法是繼續使用 .catch()
。
回想一下await
的功能:它將等待promise
完成其工作。再回想一下,promise.catch()
也將是一個promise
,所以我們可以像這樣編寫錯誤處理:
![[譯]JavaScript async / await:好處、坑和正確用法](https://i.iter01.com/images/ea2a84cf7cad1d0f28b4fff76ab51912da24d23c6298873de127d997c26ba836.png)
- 它是
promises
和await
函式的混合體。您仍然需要了解promises
的工作原理。 - 錯誤處理在正常路徑之前進行,這樣不直觀。
結論
ES7引入的關鍵字async/await
肯定是對JavaScript非同步程式設計的改進。它可以使程式碼更容易閱讀和除錯。然而,為了正確使用它們,必須完全理解promise
,因為它們只不過是語法糖,而潛在的技術仍然是promise
。