摘要:原文地址原文作者翻譯作者是在版本中引入的,它對于中的異步編程而言是一個巨大的提升。可能會產生誤導一些文章把和進行了比較,同時說它是異步編程演變過程中的下一代解決方案,對此我不敢茍同。結論在中引入的關鍵字無疑是對異步編程的一大加強。
原文地址: https://hackernoon.com/javasc...原文作者: Charlee Li
翻譯作者: Xixi20160512
async/await 是在 ES7 版本中引入的,它對于 JavaScript 中的異步編程而言是一個巨大的提升。它可以讓我們以同步的方式處理異步的流程,同時不會阻塞主線程。但是,想要用好這一特性,可能需要動點腦筋。本文中,我們將從不同的角度探討 async/await,同時會展示如何正確和高效的使用它們。
async/await 的優點async/await帶給我們最大的一個好處就是同步的編程風格。讓我們看一個例子:
// 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 函數。
所有主流瀏覽器都支持 async 函數。(圖片來源:https://caniuse.com/)
原生支持意味著你不需要編譯代碼。更重要的是,這個將有助于調試。當你在 async 方法的入口打一個斷點并且步進到 await 這一行的時候,你將會看到調試器在 bookModel.fetchAll() 這個函數執行的時候等待了一會兒,然后才會走到接下來的 .filter 這一行!和 promise 的示例比較起來,這個容易多了,因為你必須在 .filter 這一行再打一個斷點。
調試 async 函數。調試器會在 await 這一行等待執行完成然后才會移動到下一行。
另一個不那么明顯的好處就是 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 (特殊情況下),返回 null 的時候調用者不能安全的調用 .then() 。使用 async 進行聲明的時候,這個問題就不會存在了。
Async/await 可能會產生誤導一些文章把 async/await 和 Promise 進行了比較,同時說它是 JavaScript 異步編程演變過程中的下一代解決方案,對此我不敢茍同。Async/await 是一個提升,但它僅僅是一個語法糖,它將不會完全的改變我們的編程風格。
實質上,async 函數仍然是 promise。你必須理解 promises 之后才能正確的使用 async 函數,更糟糕的是,大多數情況下你必須同時使用 promises 和 async 函數。
思考一下上面例子中使用到 的 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) { // 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 以同步的方式去編寫代碼。在復雜的流程下面,直接使用 promises 可能會更簡單。
錯誤處理使用 promises 的情況下,一個異步函數會返回兩種可能的值:resolved 和 rejected。我們可以使用 .then() 來處理正常的情況 .catch() 處理異常情況。然而對于 async/await 來說,異常處理可能會有點詭異。
try...catch最標準的(也是我推薦的)處理方式是使用 try...catch 表達式。當 await 一個函數調用的時候,任何 rejected 的值都會以異常的形式拋出來。這里有個例子:
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 塊中使用任何 return 表達式等價于使用 return undefined ;同時,返回的仍是一個 resolved 的值。)
拋出這個異常,如果你希望調用者去處理它。你可以直接拋出原始的錯誤對象,例如 throw error; ,這種方式允許你以 promise 鏈式的方式使用 async getBooksByAuthorWithAwait() 方法(列如,你仍然可以像 getBooksByAuthorWithAwait().then(...).catch(error => ...) 這樣調用它);或者,你可以使用 Error 對象包裝錯誤對象,例如, throw new Error(error) ,使用這種方式可以在控制臺中展示所有的調用棧記錄。
使用 Reject,例如, return Promise.reject(error) ,這個方式等價于 throw error ,因此不推薦使用這種方式。
使用 try...catch 的優點有以下這些:
簡單,傳統。只要你有其他語言的經驗,例如 C++ 或 Java,理解這種處理方式將不會有任何困難。
你可以將多個 await 調用包裝在一個 try...catch 塊中來集中處理所有錯誤,如果每一步的錯誤處理非必要的話。
這種處理方式有一個缺陷。由于 try...catch 將會捕獲這個代碼塊中的所有異常,一些其他通常不會被 promises 捕獲的異常也會被捕獲住??紤]一下這個例子:
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 ,這些文字是黑色的。這個錯誤是 console.log() 打印出來的而不是 JavaScript 自身。某些時候這將會是致命的:如果 BookModel 被一系列函數調用深深地封閉起來了,同時,其中某一個調用將這個錯誤處理掉了,這時候就很難像這樣去發現這個錯誤了。
使函數同時返回兩個值另外一個錯誤處理的方式是由 Go 語言啟發的。它允許 async 函數同時返回錯誤的值和正常的值。可以從下面這個博客中了解到更詳細的的介紹:
[How to write async await without try-catch blocks in Javascript
ES7 Async/await allows us as developers to write asynchronous JS code that look synchronous. In current JS version we…blog.grossman.io](https://blog.grossman.io/how-...
簡而言之,你能夠像下面這樣使用 async 函數:
[err, user] = await to(UserModel.findById(1));
我個人并不喜歡這種處理方式,因為它把 Go 語言的編程風格帶到了 JavaScript 中,這樣顯得不自然,但是在某些情況下這種方式會很有用。
使用 .catch我要介紹的最后一種處理方式是仍然使用 .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); });
這種處理方式有兩個次要的問題:
這種方式混合了 promises 和 async 函數。你仍然需要理解 promises 的運行原理之后才能讀懂它。
錯誤處理在正常流程之前,這樣是不太直觀的。
結論在 ES7 中引入的 async/await 關鍵字無疑是對 JavaScript 異步編程的一大加強。它能夠把代碼變得更易于閱讀和調試。然后,為了正確的使用它們,必須要完全理解 promises,因為它們不過是語法糖,底層的技術仍然是 promises。
希望這篇文章能夠給你一些關于 async/await 的啟發,同時能夠幫助你避免一些常見的錯誤。感謝閱讀,如果喜歡的話,請為我點贊。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/104984.html
摘要:兩種格式對象對象是一個無序的名稱值對集合。數組數組是值的有序集合。值之間使用逗號分隔。這兩個方法分別用于把對象序列化為字符串和把字符串解析為原生值方法用于將字符串轉化成對象對應的表示利用將對象轉換成字符串 JSON簡介 簡介:JSON(JavaScriptObject Notation)、輕量級數據交換格式、非常適合于服務器與 JavaScript 的交互。 JSON兩種格式: 1、對...
摘要:,標題黨了,本文僅介紹相關生態和一些配置心得。函數是在時候常用的工具函數,對編譯模塊時,會將用到的放到模塊頂部。用來看最終引入了哪些必須配合,貌似加入了此項以后,會得到類似于的效果。 Babel Sorry,標題黨了,本文僅介紹 Babel 相關生態和一些配置心得。 Babel 各個 package 的用途 babel-core: 核心部分 babel-cli: 允許使用命令行 ...
摘要:輸出和字符串大小寫轉換方法,和是針對特定地區的實現。輸出輸出輸出輸出基于指定的分割符將一個字符串分割成多個子串。 1 初始化 //常用初始化方法 var stringVal = hello iFat3; //構造函數創建方法 var stringObj = new String(hello iFag3); 2 length屬性 var stringVal = hello iFat3; ...
閱讀 3140·2021-09-28 09:36
閱讀 3685·2021-09-08 09:45
閱讀 1793·2021-09-01 10:43
閱讀 3470·2019-08-30 12:44
閱讀 3345·2019-08-29 17:25
閱讀 1369·2019-08-29 11:03
閱讀 1991·2019-08-26 13:36
閱讀 693·2019-08-23 18:24