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