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

阿里雲最近在作活動,低至2折,有興趣能夠看看:
https://promotion.aliyun.com/...

爲了保證的可讀性,本文采用意譯而非直譯。javascript

想閱讀更多優質文章請猛戳GitHub博客,一年百來篇優質文章等着你!html

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

前置知識

async 做用是什麼

MDN 能夠看出:java

async 函數返回的是一個 Promise 對象。async 函數(包含函數語句、函數表達式、Lambda表達式)會返回一個 Promise 對象,若是在函數中 return 一個直接量,async 會把這個直接量經過 Promise.resolve() 封裝成 Promise 對象。c++

若是 async 函數沒有返回值, 它會返回 Promise.resolve(undefined)git

await 做用是什麼

MDN 瞭解到:github

await 等待的是一個表達式,這個表達式的計算結果是 Promise 對象或者其它值(換句話說,await 能夠等任意表達式的結果)。編程

若是它等到的不是一個 Promise 對象,那 await 表達式的運算結果就是它等到的東西。segmentfault

若是它等到的是一個 Promise 對象,await 就忙起來了,它會阻塞後面的代碼,等着 Promise 對象 resolve,而後獲得 resolve 的值,做爲 await 表達式的運算結果。promise

這就是 await 必須用在 async 函數中的緣由。async 函數調用不會形成阻塞,它內部全部的阻塞都被封裝在一個 Promise 對象中異步執行。

async/await 的優勢

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

圖片描述

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

最佳的地方不只在於可讀性。async/await 到今天爲止,全部主流瀏覽器都徹底支持異步功能。

圖片描述

本地瀏覽器的支持意味着你沒必要轉換代碼。更重要的是,它便於調試。當在函數入口點設置斷點並跨過 await 行時,將看到調試器在 bookModel.fetchAll() 執行其任務時暫停片刻,而後它將移動到下一個.filter 行,這比 promise 代碼要簡單得多,在 promise 中,必須在 .filter 行上設置另外一個斷點。

圖片描述

另外一個不太明顯的優勢是 async 關鍵字。 async聲明 getBooksByAuthorWithAwait()函數返回值確保是一個 promise,所以調用者能夠安全地使用 getBooksByAuthorWithAwait().then(...) 或await getBooksByAuthorWithAwait()。 想一想下面的例子(很差的作法!):

圖片描述

在上述代碼中,getBooksByAuthorWithPromise 可能返回 promise(正常狀況下)或 null 值(異常狀況下),在異常狀況下,調用者不能調用 .then()。有了async 聲明,這種狀況就不會出現了。

async/await 可能會產生誤導

一些文章將 async/waitPromise 進行了比較,並聲稱它是 JavaScript 下一代異步編程風格,對此做者深表異議。async/await 是一種改進,但它只不過是一種語法糖,不會徹底改變咱們的編程風格。

從本質上說,async 函數仍然是 promise。在正確使用 async 函數以前,你必須先了解 promise,更糟糕的是,大多數時候你須要在使用 promises 的同時使用 async 函數。

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

這意味着 getbooksbyauthorwithwait() 將返回一個 promise,因此也可使用 .then(...)方式來調用它。

嗯,這未必是件壞事。只有 await 的名字給人一種感受,「哦,太好了,能夠把異步函數轉換成同步函數了」,這其實是錯誤的。

async/await

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

太過串行化

儘管 await 可使代碼看起來像是同步的,但實際它們仍然是異步的,必須當心避免太過串行化。

圖片描述

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

  1. await bookModel.fetchAll() 會等待 fetchAll() 直到 fetchAll() 返回結果。
  2. 而後 await authorModel.fetch(authorId) 被調用。

注意,authorModel.fetch(authorId) 並不依賴於 bookModel.fetchAll() 的結果,實際上它們能夠並行調用!然而,用了 await,兩個調用變成串行的,總的執行時間將比並行版本要長得多得多。

下面是正確的方式:

圖片描述

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

圖片描述

簡而言之,你仍然須要將流程視爲異步的,而後使用 await 寫出同步的代碼。在複雜的流程中,直接使用 promise 可能更方便。

錯誤處理

promise中,異步函數有兩個可能的返回值: resolvedrejected。咱們能夠用 .then() 處理正常狀況,用 .catch() 處理異常狀況。然而,使用 async/await方式的,錯誤處理可能比較棘手。

try…catch

最標準的(也是做者推薦的)方法是使用 try...catch 語法。在 await 調用時,在調用 await 函數時,若是出現非正常情況就會拋出異常,await 命令後面的 promise 對象,運行結果多是 rejected,因此最好把await 命令放在 try...catch 代碼塊中。以下例子:

圖片描述

在捕捉到異常以後,有幾種方法來處理它:

  • 處理異常,並返回一個正常值。(不在 catch 塊中使用任何 return 語句至關於使用 return undefined,undefined 也是一個正常值。)
  • 若是你想讓調用者處理它,你能夠直接拋出普通的錯誤對象,如 throw errorr,它容許你在 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 會捕獲代碼塊中的每一個異常,因此一般不會被 promise 捕獲的異常也會被捕獲到。好比:

圖片描述

運行此代碼,你將獲得一個錯誤 ReferenceError: cb is not defined。這個錯誤是由console.log()打印出來的的,而不是 JavaScript 自己。有時這多是致命的:若是 BookModel 被包含在一系列函數調用中,其中一個調用者吞噬了錯誤,那麼就很難找到這樣一個未定義的錯誤。

讓函數返回兩個值

另外一種錯誤處理方法是受到Go語言的啓發。它容許異步函數返回錯誤和結果。詳情請看這篇博客文章:

How to write async await without try-catch blocks in Javascript

簡而言之,你能夠像這樣使用異步函數:

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

做者我的不喜歡這種方法,由於它將 Go 語言的風格帶入到了 JavaScript 中,感受不天然。但在某些狀況下,這可能至關有用。

使用 .catch

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

回想一下 await 的功能:它將等待 promise 完成它的工做。值得注意的一點是 promise.catch() 也會返回一個 promise ,因此咱們能夠這樣處理錯誤:

圖片描述

這種方法有兩個小問題:

  • 它是 promises 和 async 函數的混合體。你仍然須要理解 是promises 如何工做的。
  • 錯誤處理先於正常路徑,這是不直觀的。

結論

ES7引入的 async/await 關鍵字無疑是對J avaScrip t異步編程的改進。它可使代碼更容易閱讀和調試。然而,爲了正確地使用它們,必須徹底理解 promise,由於 async/await 只不過是 promise 的語法糖,本質上仍然是 promise

原文:

https://hackernoon.com/javasc...

你的點贊是我持續分享好東西的動力,歡迎點贊!

交流

乾貨系列文章彙總以下,以爲不錯點個Star,歡迎 加羣 互相學習。

https://github.com/qq44924588...

我是小智,公衆號「大遷世界」做者,對前端技術保持學習愛好者。我會常常分享本身所學所看的乾貨,在進階的路上,共勉!

關注公衆號,後臺回覆福利,便可看到福利,你懂的。

clipboard.png

相關文章
相關標籤/搜索