摘要:取而代之,利用事件循環體系,使用了一種類似語法的工作方式一旦非阻塞的異步操作完成之后,就可以讓開發者分配的回調函數被觸發。第一個嘗試嵌套的回調函數下面是使用嵌套的回調函數的實現方法這可能對于任何使用者來說再熟悉不過了。
寫在文章前
這篇文章翻譯自 ASYNC/AWAIT WILL MAKE YOUR CODE SIMPLER,這是一篇寫于2017年八月的文章,并由某專欄提名為17年十大必讀文章。翻譯的不好的地方,還望大家指出, ̄▽ ̄ 謝謝。
或者說,我如何學習不使用回調函數并且愛上ES8有時,現代JavaScript項目會脫離我們的掌控。其中一個主要的罪魁禍首就是雜亂的處理異步的任務,導致寫出了又長又復雜又深層嵌套的代碼塊。JavaScript現在提供了一個新的處理這些操作的語法,他甚至能把最錯綜復雜的操作轉化成為簡潔而且可讀性高的代碼
背景 AJAX (Asynchronous JavaScript And XML)首先來進行一點科普。 在90年代末期, Ajax是異步JavaScript的第一個重大突破。 這個技術可以讓網站在html加載之后獲取和展示新的數據。對于當時大部分網站的那種需要重新下載整個個頁面來展示一個部分內容的更新來說,它是革命性的創新。這項技術(在jQuery中通過捆綁成為輔助函數而聞名)在整個21世界主導了web開發,同時ajax在今天也是網站用來檢索數據的主要技術,但xml卻被json大規模的取代
NodeJS當NodeJS在2009年第一次發布的時候,服務端的一個主要的關注點就是允許程序優雅的處理并發。當時大部分的服務端語言使用阻塞代碼完成的這種方式來處理I/O操作,直到它結束處理I/O操作之后再繼續進行之前的代碼運行。取而代之,NodeJS利用事件循環體系,使用了一種類似ajax語法的工作方式:一旦非阻塞的異步操作完成之后,就可以讓開發者分配的回調函數被觸發。
Promises幾年之后,一個新的叫做“promises”的標準出現在nodejs和瀏覽器環境中,他提供了一套更強大也更標準化的方式去構建異步操作。promises 仍舊使用基于回調的格式,但是為異步操作的鏈式調用和構建提供了統一的語法。promises,這種由流行的開源庫所創造的標準,最終在2015年被加入了原生JavaScript。
promises雖然是一個重大的改進,但仍舊會在某些情況下產生冗長難讀的代碼。
現在,我們有了一個新的解決方案。
async/await 是一種允許我們像構建沒有回調函數的普通函數一樣構建promises的新語法(從 .net和c#借鑒而來)。 這個是一個極好的JavaScript的增加功能,在去年被加進了JavaScript ES7,它甚至可以用來簡化幾乎所有現存的js應用。
Examples我們將會舉幾個例子。
這些代碼例子不需要加載任何的三方庫。Async/await 已經在在最新版本的chrome,Firefox,Safari,和edge 獲得全面支持,所以你可以在瀏覽器的控制臺中試著運行這些示例。此外,async/await 語法可以在Node的7.6版本及其以上運行, Babel 以及TypeScript 也同樣支持async/await 語法。Async和await 如今完全可以在任何JavaScript項目中使用Setup
如果你想在你的電腦上跟隨我們的腳步探尋async,我們就將會使用這個虛擬的API Class。這個類通過返回promise對象來模擬網絡的調用的過程,并且這些promise對象將會在被調用的200ms之后使用resolve函數將簡單的數據作為參數傳遞出去。
class Api { constructor () { this.user = { id: 1, name: "test" } this.friends = [ this.user, this.user, this.user ] this.photo = "not a real photo" } getUser () { return new Promise((resolve, reject) => { setTimeout(() => resolve(this.user), 200) }) } getFriends (userId) { return new Promise((resolve, reject) => { setTimeout(() => resolve(this.friends.slice()), 200) }) } getPhoto (userId) { return new Promise((resolve, reject) => { setTimeout(() => resolve(this.photo), 200) }) } throwError () { return new Promise((resolve, reject) => { setTimeout(() => reject(new Error("Intentional Error")), 200) }) } }
每個例子將會按順序執行相同的三個操作:檢索一個用戶,檢索他們的朋友,以及檢索他們的照片。最后,我們將在控制臺輸出上述的三個結果。
第一個嘗試-嵌套的promise回調函數下面是使用嵌套的promise回調函數的實現方法
function callbackHell () { const api = new Api() let user, friends api.getUser().then(function (returnedUser) { user = returnedUser api.getFriends(user.id).then(function (returnedFriends) { friends = returnedFriends api.getPhoto(user.id).then(function (photo) { console.log("callbackHell", { user, friends, photo }) }) }) }) }
這可能對于任何JavaScript使用者來說再熟悉不過了。這個代碼塊有著非常簡單的目的,并且很長而且高層級嵌套,還以一大群的括號結尾
}) }) }) }
在真實的代碼庫中,每個回調函數都可能會相當長,這可能會導致產生一些非常冗長而且高層級嵌套的函數。我們一般管這種在回調的回調中使用回調的代碼叫“回調地獄”
更糟糕的是,沒有辦法進行錯誤檢查,所以任何一個回調都可能會作為一個未處理的Promise rejection 而引發不易察覺的地失敗。
第二個嘗試 - 鏈式promise讓我們看看我們是不是能改進一下
function promiseChain () { const api = new Api() let user, friends api.getUser() .then((returnedUser) => { user = returnedUser return api.getFriends(user.id) }) .then((returnedFriends) => { friends = returnedFriends return api.getPhoto(user.id) }) .then((photo) => { console.log("promiseChain", { user, friends, photo }) }) }
promise的一個很好的特性就是他們能夠通過在每個回調內部返回另外一個promise對象而進行鏈式操作。這個方法可以將所有的回調視作為平級的。此外,我們還可以使用箭頭函數來縮寫回調的表達式。
這個變體明顯比之前的那個嘗試更易讀,而且還有很好的序列感。然而,很遺憾,依舊很冗長,看起來還有點復雜
第三個嘗試 Async/Await有沒有可能我們不使用任何的回調函數?不可能嗎?有想過只用7行就實現它的可能性嗎?
async function asyncAwaitIsYourNewBestFriend () { const api = new Api() const user = await api.getUser() const friends = await api.getFriends(user.id) const photo = await api.getPhoto(user.id) console.log("asyncAwaitIsYourNewBestFriend", { user, friends, photo }) }
變得更好了有沒有?在promise之前調用await暫停了函數流直到promise 處于resolved狀態,然后將結果賦值給等號左邊的變量。這個方式能讓我們編寫一個就像是一個正常的同步命令一樣的異步操作流程。
我想你現在和我一樣,對這個特性感到十分的激動有沒有?!
注意“async”關鍵詞是在整個函數聲明的開始聲明的。我們必須要這么做,因為其實它將整個函數轉化成為一個promise。我們將會在稍后研究它。LOOPS(循環)
Async/await讓以前的十分復雜的操作變得特別簡單,比如說, 加入我們想按順序取回每個用戶的朋友列表該怎么辦?
第一個嘗試 - 遞歸的promise循環下面是如何按照順序獲取每個朋友列表的方式,這可能看起來很像很普通的promise。
function promiseLoops () { const api = new Api() api.getUser() .then((user) => { return api.getFriends(user.id) }) .then((returnedFriends) => { const getFriendsOfFriends = (friends) => { if (friends.length > 0) { let friend = friends.pop() return api.getFriends(friend.id) .then((moreFriends) => { console.log("promiseLoops", moreFriends) return getFriendsOfFriends(friends) }) } } return getFriendsOfFriends(returnedFriends) }) }
我們創建了一個內部函數用來通過回調鏈式的promises獲取朋友的朋友,直到列表為空。O__O 我們的確實現了功能,很棒棒,但是我們其實使用了一個十分復雜的方案來解決一個相當簡單的任務。
注意 - 使用promise.all()來嘗試簡化PromiseLoops()函數會導致它表現為一個有著完全不同的功能的函數。這個代碼段的目的是按順序(一個接著一個)運行操作,但Promise.all是同時運行所有異步操作(一次性運行所有)。但是,值得強調的是, Async/await 與Promise.all()結合使用仍舊十分的強大,就像我們下一個小節所展示的那樣。第二次嘗試- Async/Await的for循環
這個可能就十分的簡單了。
async function asyncAwaitLoops () { const api = new Api() const user = await api.getUser() const friends = await api.getFriends(user.id) for (let friend of friends) { let moreFriends = await api.getFriends(friend.id) console.log("asyncAwaitLoops", moreFriends) } }
不需要寫任何的遞歸Promise,只有一個for循環。看到了吧,這就是你的人生益友-Async/Await
PARALLEL OPERATIONS(并行操作)逐個獲取每個朋友列表似乎有點慢,為什么不采取并行執行呢?我們可以使用async/await 來實現這個需求嗎?
顯然,可以的。你的朋友它可以解決任何問題。:)
async function asyncAwaitLoopsParallel () { const api = new Api() const user = await api.getUser() const friends = await api.getFriends(user.id) const friendPromises = friends.map(friend => api.getFriends(friend.id)) const moreFriends = await Promise.all(friendPromises) console.log("asyncAwaitLoopsParallel", moreFriends) }
為了并行的運行這些操作,要先生成成運行的promise數組,并把它作為一個參數傳給Promise.all()。它返回給我們一個唯一的promise對象可以讓我們進行await, 這個promise對象一旦所有的操作都完成了就將會變成resolved狀態。
Error handling (錯誤處理)然而,這篇文章到目前為止還沒有說到那個異步編程的重要問題:錯誤處理。 很多代碼庫的災難源頭就在于異步的錯誤處理通常涉及到為每個操作寫多帶帶的錯誤處理的回調。因為將錯誤放到調用堆棧的頂部會很復雜,并且通常需要在每個回調的開始明確檢查是否有錯誤拋出。這種方法是十分繁瑣冗長而且容易出錯的。況且,在一個promise中拋出的任何異常如果沒有被正確捕獲的話,都會產生一個不被察覺的失敗,從而導致代碼庫有因為不完整錯誤檢驗而產生的“不可見錯誤”。
讓我們重新回到之前的例子中給每一種嘗試添加錯誤處理。我們將在獲取用戶圖片之前使用一個額外的函數api.throwError()來檢測錯誤處理。
第一個嘗試 - promise的錯誤回調函數讓我們來看看最糟糕的寫法:
function callbackErrorHell () { const api = new Api() let user, friends api.getUser().then(function (returnedUser) { user = returnedUser api.getFriends(user.id).then(function (returnedFriends) { friends = returnedFriends api.throwError().then(function () { console.log("Error was not thrown") api.getPhoto(user.id).then(function (photo) { console.log("callbackErrorHell", { user, friends, photo }) }, function (err) { console.error(err) }) }, function (err) { console.error(err) }) }, function (err) { console.error(err) }) }, function (err) { console.error(err) }) }
太惡心了。除了真的很長很丑這個缺點之外,控制流也是非常不直觀,因為他是從外層進入,而不是像正常的可讀性高的代碼一樣那種是由上至下的。太糟糕了,我們繼續第二個嘗試。
第二個嘗試- 鏈式promise捕獲方法我們可以通過使用一種promise-catch組合(先promise再捕獲再promise再再捕獲)的方式來改進一下。
function callbackErrorPromiseChain () { const api = new Api() let user, friends api.getUser() .then((returnedUser) => { user = returnedUser return api.getFriends(user.id) }) .then((returnedFriends) => { friends = returnedFriends return api.throwError() }) .then(() => { console.log("Error was not thrown") return api.getPhoto(user.id) }) .then((photo) => { console.log("callbackErrorPromiseChain", { user, friends, photo }) }) .catch((err) => { console.error(err) }) }
顯然比之前的好太多,通過利用鏈式promise的最后的那個單個的catch函數,我們可以為所有的操作提供單個錯誤處理。但是,依舊有點復雜,我們還是必須要使用特殊的回調函數來處理異步錯誤,而不是像處理普通的JavaScript錯誤一樣處理異步錯誤。
第三個嘗試-正常的try/catch塊我們可以做的更好。
async function aysncAwaitTryCatch () { try { const api = new Api() const user = await api.getUser() const friends = await api.getFriends(user.id) await api.throwError() console.log("Error was not thrown") const photo = await api.getPhoto(user.id) console.log("async/await", { user, friends, photo }) } catch (err) { console.error(err) } }
這里,我們將整個操作封裝在一個正常的try/catch 塊中。這樣的話,我們就可以使用同樣的方式從同步代碼和一步代碼中拋出并捕獲錯誤。顯然,簡單的多;)
Composition(組合)我在之前提到說,任何帶上async 標簽的函數實際上返回了一個promise對象。這可以讓我們組合異步控制流變得十分的簡單。
比如說,我們可以重新配置之前的那些例子來返回用戶數據而不是輸出它,然后我們可以通過調用async函數作為一個promise對象來檢索數據。
async function getUserInfo () { const api = new Api() const user = await api.getUser() const friends = await api.getFriends(user.id) const photo = await api.getPhoto(user.id) return { user, friends, photo } } function promiseUserInfo () { getUserInfo().then(({ user, friends, photo }) => { console.log("promiseUserInfo", { user, friends, photo }) }) }
更好的是,我們也可以在接收的函數中使用async/await語法,從而生成一個完全清晰易懂,甚至很精煉的異步編程代碼塊。
async function awaitUserInfo () { const { user, friends, photo } = await getUserInfo() console.log("awaitUserInfo", { user, friends, photo }) }
如果我們現在需要檢索前十個用戶的所有數據呢?
async function getLotsOfUserData () { const users = [] while (users.length < 10) { users.push(await getUserInfo()) } console.log("getLotsOfUserData", users) }
要求并發的情況下呢?還要有嚴謹的錯誤處理呢?
async function getLotsOfUserDataFaster () { try { const userPromises = Array(10).fill(getUserInfo()) const users = await Promise.all(userPromises) console.log("getLotsOfUserDataFaster", users) } catch (err) { console.error(err) } }Conclusion(結論)
隨著單頁JavaScript web程序的興起和對NodeJS的廣泛采用,如何優雅的處理并發對于JavaScript開發人員來說比任何以往的時候都顯得更為重要。Async/Await緩解了許多因為控制流問題而導致bug遍地的這個困擾著JavaScript代碼庫數十年的問題,并且幾乎可以保證讓任何異步代碼塊變的更精煉,更簡單,更自信。而且近期async/await 已經在幾乎所有的主流瀏覽器以及nodejs上面獲得全面支持,因此現在正是將這些技術集成到自己的代碼實踐以及項目中的最好時機。
討論時間加入到reddit的討論中
async/await讓你的代碼更簡單1
async/await讓你的代碼更簡單2
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/107033.html
摘要:原文地址原文作者翻譯作者是在版本中引入的,它對于中的異步編程而言是一個巨大的提升??赡軙a生誤導一些文章把和進行了比較,同時說它是異步編程演變過程中的下一代解決方案,對此我不敢茍同。結論在中引入的關鍵字無疑是對異步編程的一大加強。 原文地址: https://hackernoon.com/javasc...原文作者: Charlee Li 翻譯作者: Xixi20160512 asy...
摘要:這只是一個更優雅的得到值的語句,它比更加容易閱讀和書寫??偨Y放在一個函數前的有兩個作用使函數總是返回一個允許在這其中使用前面的關鍵字能夠使等待,直到處理結束。 Async/await 寫在前面 渣渣新人的首篇外文文章翻譯!!存在錯誤可能會很多,如有錯誤,煩請各位大大指正出來,感謝! 本篇為翻譯!本篇為翻譯!本篇為翻譯! 原文文章地址:https://javascript.info/a...
摘要:包含所有外來的事件,,,,之間的等。當定義函數時,還可以指定其運行結果返回值的類型,以提高代碼的可讀性定義了返回結果值為類型因為類型不匹配,會報錯最主要的功能就是提供了鏈式調用。 由于前面的HTTP請求用到了異步操作,不少小伙伴都被這個問題折了下腰,今天總結分享下實戰成果。Dart是一個單線程的語言,遇到有延遲的運算(比如IO操作、延時執行)時,線程中按順序執行的運算就會阻塞,用戶就會...
摘要:讓我們使用它從數組中返回一個值數組在中,我們可以這樣做,這是一種更簡單的方法最重要的部分是創建數組,該數組立即調用所有的我們在主函數中等待這些。所以在我們真正等待完成之前,主函數就退出了。 原文:https://pouchdb.com/2015/03/0... PouchDB最棘手的方面之一是它的API是異步的。在Stack Overflow、Github和IRC上,我看到了不少困惑的...
閱讀 1001·2021-11-15 18:06
閱讀 2369·2021-10-08 10:04
閱讀 2653·2019-08-28 18:03
閱讀 900·2019-08-26 13:42
閱讀 1923·2019-08-26 11:31
閱讀 2426·2019-08-23 17:13
閱讀 928·2019-08-23 16:45
閱讀 2057·2019-08-23 14:11