翻譯練習javascript
原博客地址:JavaScript async/await: The Good Part, Pitfalls and How to Usejava
ES7中引進的async/await
是對JavaScript
的異步編程的一個極大改進。它提供了一種同步代碼編寫風格來獲取異步資源的選項,卻不阻塞主進程。而後,想很好地使用它也很棘手。在這篇文章中咱們將經過不一樣的角度去探討async/await
,而後展現如何正確、有效地使用它們。編程
async/await
帶給咱們最大的好處就是同步的編碼風格。讓咱們看看下面的例子。promise
// async/await async getBooksByAuthorWithAwait(authorId) { const books = await bookModel.fetchAll(); return books.filter(b => b.authorId === authorId); } // promise getBooksByAuthorWithPromise(authorId) { return bookModel.fetchAll() .then(books => books.filter(b => b.authorId === authorId)); }
顯而易見,async/await
的版本比promise
的版本更容易理解。若是你忽略await
關鍵詞,代碼看起來就像其餘的的同步語言,好比說Python
。瀏覽器
最棒的地方不只僅是可讀性。async/await
擁有瀏覽器的原生支持。目前爲止,全部的主流瀏覽器都徹底支持async函數。安全
原生支持意味着你不用轉譯代碼。更重要的是,它便於調試。當你在函數的入口處設置一個斷點,而後步入await
這行代碼,你將看到調試器在bookModel.fetchAll()
執行的時候停了一會,而後移動到下一步.filter
代碼行。這比promise
的狀況簡單多了,在promise
的狀況下,你還須要.filter
代碼行設置一個斷點。app
另外一個不太明顯的好處就是async
關鍵詞。它聲明getBooksByAuthorWithAwait()
函數返回的值保證是一個promise
,因此調用者能夠安全的調用getBooksByAuthorWithAwait().then(...)
或者 await getBooksByAuthorWithAwait()
。想一下下面這種狀況(很差的實踐):異步
getBooksByAuthorWithPromise(authorId) { if (!authorId) { return null; } return bookModel.fetchAll() .then(books => books.filter(b => b.authorId === authorId)); }
在上面的代碼中,getBooksByAuthorWithPromise
可能返回一個promise
(正常狀況)或者返回null
(例外狀況),這種狀況下調用者沒辦法安全地調用.then()
。使用async
聲明,這種狀況變得不可能。async
一些文章對async/await
和Promise
進行比較,而後宣稱async/await
是JavaScript
異步編程進化的下一代,恕我不敢苟同。Async/await
是一種改進而不只僅是一種語法糖,它並不能徹底改變咱們的編程風格。異步編程
本質上,async
函數仍然仍是promise
。在你正確的使用async
函數以前你須要先理解promise
,更有甚者,不少時候你須要同時使用async
函數的promise
。
考慮一下上面例子中的getBooksByAuthorWithAwait()
和getBooksByAuthorWithPromises()
函數。請注意它們不只僅功能徹底相同,接口也徹底相同。
這意味着,若是你直接調用getBooksByAuthorWithAwait()
,它將返回一個promise
。
嗯,這並非一件壞事。只用await
的名字給人一種,好的,這個能夠把異步函數轉換成同步函數,但這種想法徹底是錯誤的。
那麼使用async/await
的時候可能會煩那些錯誤呢?這裏有一些常見的錯誤。
儘管await
可使你的代碼看起來像同步代碼,時刻謹記,它們仍然是異步代碼,必須當心的去避免太過於連續。
async getBooksAndAuthor(authorId) { const books = await bookModel.fetchAll(); const author = await authorModel.fetch(authorId); return { author, books: books.filter(book => book.authorId === authorId), }; }
這段代碼看起來邏輯上是正確的,但事實上它是錯誤的。
await bookModel.fetchAll()
會一直等到fetchAll()
返回。await authorModel.fetch(authorId)
被調用。請注意,authorModel.fetch(authorId)
並不依賴bookModel.fetchAll()
的結果,事實上它們能夠並行調用。然而,這裏使用await
讓這兩個函數順序執行,結果它的執行總時間比並行執行的要更長。
下面是正確的方式:
async getBooksAndAuthor(authorId) { const bookPromise = bookModel.fetchAll(); const authorPromise = authorModel.fetch(authorId); const book = await bookPromise; const author = await authorPromise; return { author, books: books.filter(book => book.authorId === authorId), }; }
或者更有甚者,你想一個接一個的獲取一個列表,你必須依靠promises
:
async getAuthors(authorIds) { // WRONG, this will cause sequential calls // const authors = _.map( // authorIds, // id => await authorModel.fetch(id)); // CORRECT const promises = _.map(authorIds, id => authorModel.fetch(id)); const authors = await Promise.all(promises); }
簡而言之,你須要異步地去思考工做量,而後使用await
同步地編寫代碼。在負責的工做流中直接使用promise
可能會更簡單。
使用promise
時,一個異步的函數有兩種可能返回的結果:resolved
值和rejected
值。咱們可使用.then
去處理正常狀況,.catch
去處理異常狀況。然而,使用async/await
時,異常處理可能比較難弄。
最標準的(也是最推薦的)方式是使用try...catch
語句。當await
一個調用時,任何rejeced
值都會被當作一個異常拋出。下面是一個例子:
class BookModel { fetchAll() { return new Promise((resolve, reject) => { window.setTimeout(() => { reject({'error': 400}) }, 1000); }); } } // async/await async getBooksByAuthorWithAwait(authorId) { try { const books = await bookModel.fetchAll(); } catch (error) { console.log(error); // { "error": 400 } }
被捕獲到的錯誤正是被rejected
的值。在咱們捕獲到異常之後,咱們有如下幾個處理方式:
catch
代碼塊中不返回任何值至關一返回undefined
,這也算一個正常值。)throw error
這樣直接拋出整個error
對象,這容許你在promise
鏈中去使用async getBooksByAuthorWithAwait()
函數(例如:你仍然能夠像這樣去調用getBooksByAuthorWithAwait().then(...).catch(error => ...))
;或者你可使用Error
對象對錯誤進行包裝,像這樣:throw new Error(error)
,這樣,當錯誤在控制條打印出來的時候,這給你提供一個完整的錯誤堆棧跟蹤。Reject
它,像return Promise.reject(error)
這樣。這根throw error
同樣,因此不推薦這樣作。使用try...catch
的好處以下:
Java
或者 C++
,沒有任何困難去理解它。try...catch
中去包裹多個await
調用,這樣能夠在一個地方去處理錯誤。這種作法也有一個缺陷。由於try...catch
會捕獲全部的異常,一些不常常被promise捕獲的錯誤也會被捕獲。想一下下面這個例子:
class BookModel { fetchAll() { cb(); // note `cb` is undefined and will result an exception return fetch('/books'); } } try { bookModel.fetchAll(); } catch(error) { console.log(error); // This will print "cb is not defined" }
運行這段代碼,你在控制檯會獲得一個黑色字體的錯誤信息:ReferenceError: cb is not defined
。這個錯誤是consol.log
輸出的,而不是JavaScript
輸出的。有時候這會是致命的:若是BookModel
被一系列的函數調用深深包裹着,而其中的一個調用吞掉了這個錯誤,那麼找到一個像這樣的未定義錯誤將會十分困難。
還有一種錯誤處理方式是受到了Go
語言的啓發。它容許異步函數同時返回錯誤和結果。
簡而言之,你能夠這樣使用異步函數:
[err, user] = await to(UserModel.findById(1));
咱們將要介紹的最後一種方法是繼續使用catch
。
回想一下await
的功能:它會等待promise
完成它的工做。同時也回想一下promise.catch()
也會返回一個promise
。因此咱們能夠這樣去寫錯誤處理:
// books === undefined if error happens, // since nothing returned in the catch statement let books = await bookModel.fetchAll() .catch((error) => { console.log(error); })
這種作法有兩個小問題:
promise
和異步函數混合使用。爲了讀懂它,你仍須要瞭解promise
是如何工做的。ES7
引進的async/await
關鍵詞對JavaScript
的異步編程來講絕對是一個進步。它使得代碼的閱讀和調試都更簡單。然而,爲了正確的使用它,你必須去徹底的理解promise
,由於它不在僅僅是語法糖,它的底層原理仍然是promise
。
但願這篇文章能讓你對async/await
有一些想法,也能讓你避免一些常見的錯誤。