咱們知道,JavaScript做爲瀏覽器的腳本語言,起初是爲了與用戶交互和操做DOM,爲了不由於同時操做了同一DOM節點而引發衝突,被設計成爲一種單線程語言。而單線程語言最大的特性就是同一時間只能作一件事,這個任務未完成下一個任務就要等待,這樣無疑是對資源的極大浪費,並且嚴重時會引發阻塞,形成用戶體驗極差。這個時候就引出了異步的概念,而異步的核心就是事件循環機制Event Loop。node
JavaScript的任務分兩種,分別是同步任務和異步任務。segmentfault
如上圖所示:promise
不斷重複以上步驟,就造成了事件循環(Event Loop)瀏覽器
<script> console.log('start') setTimeout(function () { console.log('setTimeout') }, 0) console.log('end') </script>
結合上面步驟分析下這個例子:異步
1. 執行主線程同步任務,輸出start【1】,繼續往下執行 2. 遇到setTimeout,進入event table註冊setTimeout回調,setTimeout回調執行完後,繼續往下執行 3. 輸出end【2】,同步任務執行完畢 4. 進入event queue,檢查是否有可執行任務,取出event queue中setTimeout任務開始執行,輸出setTimeout【3】
結果依次爲:start -> end -> setTimeoutasync
在瀏覽器和node中的事件循環與執行機制是不一樣的,要注意區分,不要搞混。
瀏覽器環境的異步任務分爲宏任務(macroTask)和微任務(microtask),當知足條件時會分別被放進宏任務隊列和微任務隊列(先進先出),等待被執行。函數
執行過程以下:oop
如圖所示:post
1. 把總體的script代碼做爲宏任務執行 2. 執行過程當中若是遇到宏任務和微任務,知足條件時分別添加至宏任務隊列和微任務隊列 3. 執行完一個宏任務後,取出全部微任務依次執行,若是微任務一直有新的被添加進來,則一直執行,直到把微任務隊列清空 4. 不斷重複2和3,直到全部任務被清空,結束執行。
<script> console.log('start') setTimeout(() => { console.log('timer1') Promise.resolve().then(() => { console.log('promise1') }) }, 0) setTimeout(() => { console.log('timer2') Promise.resolve().then(() => { console.log('promise2') }) }, 0) setTimeout(() => { console.log('timer3') Promise.resolve().then(() => { console.log('promise3') }) }, 0) new Promise(function(resolve) { console.log('promise4'); resolve(); }).then(function() { console.log('promise5') }) console.log('end') </script>
分析:測試
第一輪:
第二輪:
第三輪:
第四輪:
如今宏任務對列和微任務隊列都被清空了,完成執行,結果爲:start > promise4 > end > promise5 > timer1 > promise1 > timer2 > promise2 > timer3 > promise3
await表達式的運算結果取決於它右側的結果
當遇到await時,會阻塞函數體內部處於await後面的代碼,跳出去執行該函數外部的同步代碼,當外部同步代碼執行完畢,再回到該函數內部執行剩餘的代碼
補充aynsc的一點知識:若是aynsc函數中return一個直接量,async 會把這個直接量經過Promise.resolve()封裝成Promise對象,若是什麼都沒return,會被封裝成Promise.resolve(undefined)
那麼 引入了async await以後的執行過程是怎樣的呢?
<script> async function async1() { console.log("async1 start"); await async2(); console.log("async1 end"); } async function async2() { console.log("async2"); } console.log("script start"); setTimeout(function () { console.log("setTimeout"); }, 0); async1(); new Promise(function (resolve) { console.log("promise1"); resolve(); }).then(function () { console.log("promise2"); }); console.log("script end"); </script>
分析:
第一輪:
進入aynsc1函數中,執行同步代碼輸出:async1 start【2】,遇到await從右向左執行,進入async2函數,輸出:async2【3】;aynsc2函數體中未返回任何東西等價於返回了Promise.resolve(undefined)
,拿到返回值後進入aynsc1函數體中,繼續執行剩下的部分,這時候aynsc1中註釋部分等價於:
async function async1() { console.log("async1 start"); //await async2(); //console.log("async1 end"); await new Promise((resolve) => resolve()).then(resolve => { console.log('async1 end') }) }
將Promise.then@1推入到微任務隊列;
第二輪:
全部任務隊列均爲空,結束執行,輸出結果爲:script start > async1 start > async2 > promise1 > script end > async1 end > promise2 > setTimeout
補充谷歌瀏覽器測試結果:
借用一個例子:await一個直接值的狀況
<script> console.log('1') async function async1() { console.log('2') await 'await的結果' console.log('5') } async1() console.log('3') new Promise(function (resolve) { console.log('4') resolve() }).then(function () { console.log('6') }) </script>
分析:
第一輪:
任務隊列爲空,執行完畢,結果爲: 1 > 2 > 3 > 4 > 5 > 6
再借個例子,這個有點複雜
<script> setTimeout(function () { console.log('8') }, 0) async function async1() { console.log('1') const data = await async2() console.log('6') return data } async function async2() { return new Promise(resolve => { console.log('2') resolve('async2的結果') }).then(data => { console.log('4') return data }) } async1().then(data => { console.log('7') console.log(data) }) new Promise(function (resolve) { console.log('3') resolve() }).then(function () { console.log('5') }) </script>
分析:
第一輪:
開始執行第一輪微任務,取出Promise.then@1,輸出:4【4】,此時async2函數執行完畢,進入aynsc1函數,此時改動下aynsc1函數,等價於:
async function async1() { console.log('1') //const data = await async2() //console.log('6') const data = await new Promise(resolve => resolve('async2的結果')).then((resolve) => { console.log(6); return resolve; }) return data; }
將上面promise.then@3推入微任務隊列中,此時:
async1().then(...)
,將async1().then@1推到微任務隊列中,取出async1().then@1,輸出:7【7】和 'async2的結果'【8】;第二輪:
因此任務被執行完畢,結果爲:1 > 2 > 3 > 4 > 5 > 6 > 7 > async2的結果 > 8
------------------------ END ----------------------------
PS: 好記性不如爛筆頭,看了那麼多資料,仍是想總結一下,否則過一陣子就忘記了,若是辛苦指出哦,謝謝~
參考資料:
理解 JavaScript 的 async/await
瀏覽器和Node不一樣的事件循環(Event Loop)
Event Loop 原來是這麼回事
這一次,完全弄懂 JavaScript 執行機制
從event loop到async await來了解事件循環機制...