這篇文章主要介紹了詳解ES6之async+await 同步/異步方案,本文以最簡明的方式來疏通async + await,有興趣的可以了解下
異步編程一直是JavaScript 編程的重大事項。關於非同步方案, ES6 先是出現了 基於狀態管理的 Promise,然後出現了 Generator 函數 + co 函數,緊接著又出現了 ES7 的 async + await 方案。
本文力求以最簡明的方式來疏通 async + await。
非同步程式設計的幾個場景
先從一個常見問題開始:一個for 迴圈中,如何非同步的列印迭代順序?
我們很容易想到用閉包,或是 ES6 規定的 let 區塊級作用域來回答這個問題。
for (let val of [1, 2, 3, 4]) { setTimeout(() => console.log(val),100); } // => 预期结果依次为:1, 2, 3, 4
這裡描述的是一個均勻發生的的非同步,它們依序依照既定的順序排在非同步佇列中等待執行。
如果非同步不是均勻發生的,那麼它們被註冊在非同步佇列中的順序就是亂序的。
for (let val of [1, 2, 3, 4]) { setTimeout(() => console.log(val), 100 * Math.random()); } // => 实际结果是随机的,依次为:4, 2, 3, 1
回傳的結果是亂序不可控的,這本來就是最真實的非同步。但另一種情況是,在循環中,如果希望前一個非同步執行完畢、後一個非同步再執行,該怎麼辦?
for (let val of ['a', 'b', 'c', 'd']) { // a 执行完后,进入下一个循环 // 执行 b,依此类推 }
這不就是多個非同步 「串列」嗎!
在回呼 callback 嵌套非同步操作、再回呼的方式,不就解決了這個問題!或者,使用 Promise + then() 層層嵌套同樣也能解決問題。但是,如果硬是要將這種嵌套的方式寫在循環中,還恐怕還需費一番周折。試問,有更好的辦法嗎?
非同步同步化方案
試想,如果要去將一批資料傳送到伺服器,只有前一批發送成功(即伺服器回傳成功的回應),才開始下一批資料的發送,否則終止發送。這就是一個典型的 “for 迴圈中存在相互依賴的非同步操作” 的範例。
明顯,這種 「串列」 的非同步,實質上可以當成同步。它和亂序的非同步比較起來,花了更多的時間。照理說,我們希望程式非同步執行,就是為了 「跳過」 阻塞,較少時間花銷。但與之相反的是,如果需要一系列的非同步 “串行”,我們應該怎樣很好的進行程式設計?
對於這個 「串列」 非同步,有了 ES6 就非常容易的解決了這個問題。
async function task () { for (let val of [1, 2, 3, 4]) { // await 是要等待响应的 let result = await send(val); if (!result) { break; } } } task();
從字面上看,就是本次循環,等有了結果,再進行下一次循環。因此,循環每執行一次就會被暫停(「卡住」)一次,直到循環結束。這種編碼實現,很好的消除了層層嵌套的 “回調地獄” 問題,降低了認知難度。
這就是非同步問題同步化的方案。關於這個方案,如果說 Promise 主要解決的是非同步回呼問題,那麼 async + await 主要解決的就是將非同步問題同步化,降低非同步程式設計的認知負擔。
async + await 「外異內同」
當早先接觸這套API 時,看著繁瑣的文檔,一知半解的認為async + await 主要用來解決異步問題同步化的。
其實不然。從上面的例子看到:async 關鍵字聲明了一個 非同步函數,這個 非同步函數 體內有一行 await 語句,它告示了該行為同步執行,並且與上下相鄰的程式碼是依次逐行執行的。
將這個形式化的東西再翻譯一下,就是:
1、async 函數執行後,總是傳回了一個promise 物件
2、await 所在的那一行語句是同步的
其中,1 說明了從外部看,task 方法執行後返回一個Promise 對象,正因為它返回的是Promise,所以可以理解task 是一個非同步方法。毫無疑問它是這樣用的:
task().then((val) => {alert(val)}) .then((val) => {alert(val)})
2 說明了在 task 函數內部,非同步已經被 「削」 變成了同步。整個就是一個執行稍微耗時的函數而已。
綜合 1、2,從形式上看,就是 “task 整體是一個非同步函數,內部整個是同步的”,簡稱“外異內同”。
整體是一個非同步函數 不難理解。在實作上,我們不妨逆向一下,語言層面讓async關鍵字呼叫時,在函數執行的末尾強制增加一個promise 反回:
async fn () { let result; // ... //末尾返回 promise return isPromise(result)? result : Promise.resolve(undefined); }
內部是同步的是怎麼做到的?實際上await 調用,是讓後邊的語句(函數)做了一個遞歸執行,直到獲取到結果並使其狀態變更,才會resolve 掉,而只有resolve 掉,await 那一行程式碼才算執行完,才繼續往下一行執行。所以,儘管外部是一個大大的 for 循環,但整個 for 迴圈是依序串列的。
因此,僅從上述框架的外觀出發,就不難理解 async + await 的意義。使用起來也就這麼簡單,反而 Promise 是個必須掌握的基礎件。
秉承本次《重读 ES6》系列的原则,不过多追求理解细节和具体实现过程。我们继续巩固一下这个 “形式化” 的理解。
async + await 的进一步理解
有这样的一个异步操作 longTimeTask,已经用 Promise 进行了包装。借助该函数进行一系列验证。
const longTimeTask = function (time) { return new Promise((resolve, reject) => { setTimeout(()=>{ console.log(`等了 ${time||'xx'} 年,终于回信了`); resolve({'msg': 'task done'}); }, time||1000) }) }
async 函数的执行情况
如果,想查看 async exec1 函数的返回结果,以及 await 命令的执行结果:
const exec1 = async function () { let result = await longTimeTask(); console.log('result after long time ===>', result); } // 查看函数内部执行顺序 exec1(); // => 等了 xx 年,终于回信了 // => result after long time ===> Object {msg: "task done"} //查看函数总体返回值 console.log(exec1()); // => Promise {[[PromiseStatus]]: "pending",...} // => 同上
以上 2 步执行,清晰的证明了 exec1 函数体内是同步、逐行逐行执行的,即先执行完异步操作,然后进行 console.log() 打印。而 exec1() 的执行结果就直接是一个 Promise,因为它最先会蹦出来一串 Promise ...,然后才是 exec1 函数的内部执行日志。
因此,所有验证,完全符合 整体是一个异步函数,内部整个是同步的 的总结。
await 如何执行其后语句?
回到 await ,看看它是如何执行其后边的语句的。假设:让 longTimeTask() 后边直接带 then() 回调,分两种情况:
1)then() 中不再返回任何东西
2) then() 中继续手动返回另一个 promise
const exec2 = async function () { let result = await longTimeTask().then((res) => { console.log('then ===>', res.msg); res.msg = `${res.msg} then refrash message`; // 注释掉这条 return 或 手动返回一个 promise return Promise.resolve(res); }); console.log('result after await ===>', result.msg); } exec2(); // => 情况一 TypeError: Cannot read property 'msg' of undefined // => 情况二 正常
首先,longTimeTask() 加上再多得 then() 回调,也不过是放在了它的回调列队 queue 里了。也就是说,await 命令之后始终是一条 表达式语句,只不过上述代码书写方式比较让人迷惑。(比较好的实践建议是,将 longTimeTask 方法身后的 then() 移入 longTimeTask 函数体封装起来)
其次,手动返回另一个 promise 和什么也不返回,关系到 longTimeTask() 方法最终 resolve 出去的内容不一样。换句话说,await 命令会提取其后边的promise 的 resolve 结果,进而直接导致 result 的不同。
值得强调的是,await 命令只认 resolve 结果,对 reject 结果报错。不妨用以下的 return 语句替换上述 return 进行验证。
return Promise.reject(res);
最后
其实,关于异步编程还有很多可以梳理的,比如跨模块的异步编程、异步的单元测试、异步的错误处理以及什么是好的实践。All in all, 限于篇幅,不在此汇总了。最后,async + await 确实是一个很优雅的方案。
以上是ES6之async+await同步/非同步方案詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

是的,JavaScript的引擎核心是用C語言編寫的。 1)C語言提供了高效性能和底層控制,適合JavaScript引擎的開發。 2)以V8引擎為例,其核心用C 編寫,結合了C的效率和麵向對象特性。 3)JavaScript引擎的工作原理包括解析、編譯和執行,C語言在這些過程中發揮關鍵作用。

JavaScript是現代網站的核心,因為它增強了網頁的交互性和動態性。 1)它允許在不刷新頁面的情況下改變內容,2)通過DOMAPI操作網頁,3)支持複雜的交互效果如動畫和拖放,4)優化性能和最佳實踐提高用戶體驗。

C 和JavaScript通過WebAssembly實現互操作性。 1)C 代碼編譯成WebAssembly模塊,引入到JavaScript環境中,增強計算能力。 2)在遊戲開發中,C 處理物理引擎和圖形渲染,JavaScript負責遊戲邏輯和用戶界面。

JavaScript在網站、移動應用、桌面應用和服務器端編程中均有廣泛應用。 1)在網站開發中,JavaScript與HTML、CSS一起操作DOM,實現動態效果,並支持如jQuery、React等框架。 2)通過ReactNative和Ionic,JavaScript用於開發跨平台移動應用。 3)Electron框架使JavaScript能構建桌面應用。 4)Node.js讓JavaScript在服務器端運行,支持高並發請求。

Python更適合數據科學和自動化,JavaScript更適合前端和全棧開發。 1.Python在數據科學和機器學習中表現出色,使用NumPy、Pandas等庫進行數據處理和建模。 2.Python在自動化和腳本編寫方面簡潔高效。 3.JavaScript在前端開發中不可或缺,用於構建動態網頁和單頁面應用。 4.JavaScript通過Node.js在後端開發中發揮作用,支持全棧開發。

C和C 在JavaScript引擎中扮演了至关重要的角色,主要用于实现解释器和JIT编译器。1)C 用于解析JavaScript源码并生成抽象语法树。2)C 负责生成和执行字节码。3)C 实现JIT编译器,在运行时优化和编译热点代码,显著提高JavaScript的执行效率。

JavaScript在現實世界中的應用包括前端和後端開發。 1)通過構建TODO列表應用展示前端應用,涉及DOM操作和事件處理。 2)通過Node.js和Express構建RESTfulAPI展示後端應用。

JavaScript在Web開發中的主要用途包括客戶端交互、表單驗證和異步通信。 1)通過DOM操作實現動態內容更新和用戶交互;2)在用戶提交數據前進行客戶端驗證,提高用戶體驗;3)通過AJAX技術實現與服務器的無刷新通信。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

SAP NetWeaver Server Adapter for Eclipse
將Eclipse與SAP NetWeaver應用伺服器整合。

DVWA
Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

記事本++7.3.1
好用且免費的程式碼編輯器

VSCode Windows 64位元 下載
微軟推出的免費、功能強大的一款IDE編輯器