ES6之async+await 同步/非同步方案
這篇文章主要介紹了詳解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中文網其他相關文章!

熱AI工具

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

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

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

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

熱門文章

熱工具

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

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

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

一般來說,我們只需要同時使用耳機或音響的其中一個設備,但是有些朋友反映在win11系統中,遇到了耳機和音響一起響的問題,其實我們可以在realtek面板中將它關閉,就可以了,下面一起來看一下吧。 win11耳機和音響一起響怎麼辦1、先在桌面上找到並打開“控制面板”2、進入控制面板,在其中找到並打開“硬體和聲音”3、然後再找到一個喇叭圖標的“Realtek高清晰音訊管理器”4、選擇“揚聲器”再點擊“後面板”進入揚聲器設定。 5.打開之後我們可以看到設備類型,如果要關閉耳機就取消勾選“耳機”,如果要

當您在您的同步資料夾中發現一個或多個項目與Outlook中的錯誤訊息不符時,這可能是因為您更新或取消了會議項目。在這種情況下,您會看到一條錯誤訊息,提示您的本機資料版本與遠端副本有衝突。這種情況通常發生在Outlook桌面應用程式中。您同步的資料夾中的一個或多個項目不符。若要解決衝突,請開啟這些項目,然後重試此操作。修復同步的資料夾中的一個或多個項目不符合Outlook錯誤在Outlook桌面版中,當本機行事曆項目與伺服器副本發生衝突時,可能會遇到問題。不過,幸運的是,有一些簡單的方法可以幫助您

人工智慧試圖模仿人類智慧的運算系統,包括人類一些與智慧具有直覺聯繫的功能,例如學習、解決問題以及理性地思考和行動。在廣義地解釋上,AI 一詞涵蓋了許多密切相關的領域如機器學習。那些大量使用 AI 的系統在醫療保健、交通運輸、金融、社交網路、電子商務和教育等領域都產生了重大的社會影響。這種日益增長的社會影響,也帶來了一系列風險和擔憂,包括人工智慧軟體中的錯誤、網路攻擊和人工智慧系統安全等面向。因此,AI 系統的驗證問題以及更廣泛的可信 AI 的話題已經開始引起研究界的關注。 「可驗證 AI」已經確

MySQL是一個非常受歡迎的開源關聯式資料庫管理系統,廣泛應用於各種Web應用、企業系統等。在現代業務的應用場景下,大多數的MySQL資料庫需要部署在多台伺服器上,以提供更高的可用性和效能,這就需要進行MySQL資料的遷移和同步。本文將介紹如何實作多台伺服器之間的MySQL資料遷移和同步。一.MySQL資料遷移MySQL資料遷移指的是將MySQL伺服器中的數

win10剪貼簿有個非常好用的功能就是跨裝置雲端儲存功能,非常的好用可以幫助用戶PC設備和手機設備同步複製貼上。設定的方法非常簡單,只要在系統裡的剪切板設置就好。 win10剪貼簿同步到手機1、先點選左下角的開始,進入設定。 2、然後去點選「系統」。 3.選擇左側的「剪貼簿」。 4.最後在右邊的「跨裝置同步」點選登錄,然後選擇手機就好了。

您系統上的 OneDrive 應用程式將所有檔案和資料夾儲存在雲端。但有時用戶不希望某些檔案或資料夾被儲存並佔用限制為 5 GB 的 OneDrive 空間而無需訂閱。為此,OneDrive 應用程式中有一個設置,允許使用者選擇要在雲端上同步的文件或資料夾。如果您也在尋找這個,那麼這篇文章將幫助您在 Windows 11 系統的 OneDrive 中選擇要同步的資料夾或檔案。如何在 Windows 11 的 OneDrive 中選擇要同步的某些資料夾注意:確保 OneDrive 應用程式已連接並同步

PHP高並發處理中的線程池優化方案隨著互聯網的快速發展和用戶需求的不斷增長,高並發成為了現代Web應用開發中的一個重要問題。在PHP中,由於其單執行緒的特性,處理高並發請求是一項挑戰。為了解決這個問題,引入線程池的概念是一個有效的最佳化方案。執行緒池是一種可重複利用的執行緒集合,用於執行大量的並發任務。它的基本思想是將線程的創建、銷毀和管理分離出來,並透過復用線程來減

並發程式設計中的鎖定與同步在並發程式設計中,多個行程或執行緒同時執行,這可能會導致資源爭用和不一致性問題。為了解決這些問題,需要使用鎖定和同步機制來協調對共享資源的存取。鎖的概念鎖是一種機制,它允許一次只有一個執行緒或程序存取共享資源。當一個執行緒或程序獲得鎖時,其他執行緒或程序將被阻止存取該資源,直到鎖被釋放。鎖的類型python中有幾種類型的鎖:互斥鎖(Mutex):確保一次只有一個執行緒或程序可以存取資源。條件變數:允許執行緒或行程等待某個條件,然後取得鎖定。讀寫鎖:允許多個執行緒同時讀取資源,但只允許一個執行緒寫入資
