最近公事甚多,很久沒學習了,本身也擼了個小網站,歡迎 star。javascript
JavaScript async/await: The Good Part, Pitfalls and How to Usejava
ES7 推出的 async/await
特性對 JS 的異步編程是一個重大的改進。在不阻塞主線程的狀況下,它爲咱們提供了使用同步代碼風格去異步獲取資源的能力。固然使用它也是須要一些技巧,這篇文章咱們從不一樣角度去探索 async/await
,爲你展現如何正確、高效的使用它們。編程
async/await
優勢它最大的優勢就是給咱們帶來同步代碼風格。見代碼:json
// 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
。promise
優勢不只僅是可讀性,async/await
已經被瀏覽器原生支持。現在,全部主流瀏覽器已經徹底支持。瀏覽器
原生支持,意味着你沒必要轉換代碼,而更重要的是有利於調試。當你在函數的 await
代碼行打上斷點,而後步進到下一行時,你會發現調試器在 bookModel.fetchAll()
操做的時候進行了短暫的停留,而後才真正的步進到 .filter
代碼行!這比 promise
調試更方便,由於你須要在 .fliter
代碼行再打一個斷點。安全
另外一個不多被人注意到的優勢是 async
關鍵字。它代表了 getBooksByAuthorWithAwait()
函數的返回值必定是個 promise
,因此它的調用者可使用 getBooksByAuthorWithAwait().then(...)
或者安全的使用 await getBooksByAuthorWithAwait()
。見代碼(錯誤的實踐!):babel
getBooksByAuthorWithPromise(authorId) {
if (!authorId) {
return null;
}
return bookModel.fetchAll()
.then(books => books.filter(b => b.authorId === authorId));
}
}
複製代碼
上面的代碼段中,getBooksByAuthorWithPromise
可能會返回一個 promise
(正常狀況)或者 null
值(異常狀況),然後者這種狀況,調用者沒法安全的使用 .then()
。而有了 async
聲明,就會避免這種不肯定性。異步
async/await
有時具備誤導性一些文章會比較 async/await
和 promise
並聲稱它是下一代 JS
異步編程,而我不一樣意這種觀點。async/await
的確是一種改進,但它不過是個語法糖,不會完全改變咱們的編程風格。async
本質來講,async
函數仍然是 promises
。在正確的使用 async
以前,你須要理解 promise
,可能你在使用 async
的過程當中也須要使用到 promise
。
回顧一下上面代碼中的 getBooksByAuthorWithAwait()
和 getBooksByAuthorWithPromises()
函數,他們不只功能徹底相同,並且具備相同的接口。
這意味着,直接調用 getBooksByAuthorWithAwait()
會返回一個 promise
。
這不見得是件壞事,而多數人認爲 await
可讓異步函數變爲同步函數的想法纔是錯誤的。
async/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) {
// 錯誤,這會致使`串行執行`
// const authors = _.map(
// authorIds,
// id => await authorModel.fetch(id));
// 正確
const promises = _.map(authorIds, id => authorModel.fetch(id));
const authors = await Promise.all(promises);
}
複製代碼
簡而言之,你仍然須要把工做流當成是異步的,而後嘗試使用 await
去寫同步代碼。在更加複雜的工做流中,直接使用 promise
可能更方便。
結合 promises
,一個異步函數只有兩個可能的返回值:resolve值
和reject值
,而後咱們可使用 .then()
處理正常狀況、.catch()
處理異常狀況。可是 async/await
的錯誤處理就須要點技巧了。
try...catch
最多見(也是我推薦)的方法就是使用 try..catch
。當 await
一個操做時,操做中任何 reject值
都會看成異常拋出。見代碼:
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 }
}
複製代碼
輸出的錯誤對象正是 reject值
。捕獲異常以後,咱們可使用以下方法處理它們:
catch
代碼塊不使用 return
語句等同於 return undefined;
,固然這也算是個正常值)。throw error
,這樣容許你在 async getBooksByAuthorWithAwait()
函數上使用 promise
鏈式操做(即:getBooksByAuthorWithAwait().then(...).catch(error => ...)
);或者使用 Error
對象包裝你的錯誤對象,如 throw new Error(error)
,這樣在控制檯查看錯誤時,你能夠看到完整的堆棧記錄。reject
錯誤對象,如 return Promise.reject(error)
。這等同於第一種作法,因此不推薦。使用 try...catch
的好處以下:
Java
或 C++
編程語言經歷,理解起來不費事。try...catch
代碼塊中你能夠在 try
代碼塊包裹多行 await
語句,而且若是前置錯誤處理沒有必要的話,你能夠在一個地方(即 catch
代碼塊)處理錯誤。這個方案仍然有它的瑕疵,try...catch
能夠捕獲代碼塊內的全部錯誤,包括那些不被 promises
捕獲的錯誤。見代碼:
class BookModel {
fetchAll() {
cb(); // `cb` 由於沒有被定義全部會致使異常
return fetch('/books');
}
}
try {
bookModel.fetchAll();
} catch(error) {
console.log(error); // 這裏打印 "cb is not defined"
}
複製代碼
運行這段代碼,你會在控制檯獲得 ReferenceError: cb is not defined
黑色字體輸出信息。你要知道,這裏的錯誤是經過 console.log()
輸出的,並非 JS
自己拋出(JS
拋出錯誤是紅色字體)。有時這會很致命:若是 BookModel
被其它一些函數調用深深嵌套、包裹,其中一個調用吞併異常,那麼想找到例子中的這種錯誤就會變得極其困難。
受 Go
語言啓發,另外一種處理錯誤的方法就是容許 async
函數返回異常
和結果
兩個值(請參閱 How to write async await without try-catch blocks in Javascript),即你能夠這樣使用 async
函數:
[err, user] = await to(UserModel.findById(1));
複製代碼
我我的不建議使用這種實現,由於它把 Go
語言的風格帶到了 JS
,這讓我感受很不天然,可是個別狀況下,使用它是極其合適的。
.catch()
最後一個方法就是繼續使用 .catch()
。
回想一下 await
的做用:它等待 promise
完成工做,也請記住 promise.catch()
也會返回一個 promise
!因此咱們能夠這些處理錯誤:
// 若是發生異常,可是 catch 語句沒有顯示返回,那麼 books === undefined
let books = await bookModel.fetchAll()
.catch((error) => { console.log(error); });
複製代碼
這個實現有兩個瑕疵:
promise
和 async
的混合函數。你須要理解 promise
才能讀懂它。ES7
的 async/await
特性對 JS
異步編程是個巨大的改進。它讓代碼可讀性更好、更方便調試。可是想要正確的使用他們,你必須完全瞭解 promise
。由於它只是個語法糖,它依賴的技術仍然是 promise
。