【JS】1034- Event Loop :你知道它們的打印順序嗎?

前言

《瀏覽器知識點整理(十三)不同的回調執行時機:宏任務和微任務》[1] 這篇文章的後面有幾道打印面試題,覺得不夠過癮,於是又找了一些對應的面試題來,還在不同的 Node.js 版本上面跑了跑,希望你也能過過癮。

先來回顧一下瀏覽器 Event Loop 的過程:

  • 先執行 當前調用棧中的同步代碼(一個宏任務);
  • 調用棧爲空後去檢查是否有異步任務(微任務)需要執行
  • 如果有則 執行完當前異步代碼(當前宏任務中微任務隊列裏的所有微任務),
  • 再之後就是 從消息隊列中取出下一個宏任務 去執行(重新維護一個調用棧),然後開始新一輪的 Event Lopp。

然後不同版本的 Node.js 的一個 Event Loop 區別:

  • 如果是 Node 10 及其之前版本:宏任務隊列當中有幾個宏任務,是要等到宏任務隊列當中的所有宏任務全部執行完畢纔會去執行微隊列當中的微任務。
  • 如果是 Node 11 及之後版本:一旦執行一個階段裏對應宏隊列當中的一個宏任務( setTimeoutsetIntervalsetImmediate 三者其中之一,不包括 I/O)就立刻執行微任務隊列,執行完微隊列當中的所有微任務再回到剛纔的宏隊列執行下一個宏任務。這就 跟瀏覽器端運行一致 了。

爲了過這個癮,我意用 nvmnvm install 10.13.0)安裝了 10.13.0 版本的 Node.js

image.png

參賽選手主要是宏任務代表 setTimeout 和微任務扛把子 Promise 及新貴 async/awiat

基礎版本

首先是單個任務的版本:

setTimeout

console.log('script start');
setTimeout(() =>  {
  console.log('setTimeout1')
}, 100)
setTimeout(() =>  {
  console.log('setTimeout2')
}, 50)
console.log('script end');
複製代碼

在 Chrome 中有一個 ProcessDelayTask 函數,該函數會根據發起時間和延遲時間計算出到期的任務,然後依次執行這些到期的任務。執行順序如下:

image.png

Promise

console.log('script start');
new Promise((resolve) =>  {
  console.log('promise1');
  resolve();
  console.log('promise1 end');
}).then(() => {
  console.log('promise2');
})
console.log('script end');
複製代碼

Promise 內部是 同步執行 的,所以會有以下打印順序:

image.png

async/awiat

async function async1() {
  console.log('async1 start')
  await async2();
  console.log('async1 end')
}
async function async2() {
  console.log('async2')
}
console.log('script start')
async1();
console.log('script end')
複製代碼

執行順序如下,這裏需要注意的是 async2 函數也是同步執行的。

image.png

以上的基礎版本就是同步和異步的區別了,因爲是單個任務,也沒有其它複雜的場景,在 Node.js 中的表現和瀏覽器是一樣的。

組合版本一(setTimeoutPromise

console.log('script start');
setTimeout(() =>  {
  console.log('setimeout');
}, 0)
new Promise((resolve) =>  {
  console.log('promise1');
  resolve();
  console.log('promise1 end');
}).then(() => {
  console.log('promise2');
})
console.log('script end');
複製代碼

在 Chrome 中的執行順序:

image.png

這個版本就是簡單的宏任務 setTimeout 和微任務 Promise 的組合了,因爲也就一個宏任務和一個微任務,它在不同版本中的 Node.js 裏面表現也是和在瀏覽器一樣的。

組合版本二(setTimeoutPromiseasync/await

console.log('script start')
async function async1() {
  console.log('async1 start')
  await async2()
  console.log('async1 end')
}
async function async2() {
  console.log('async2')
}
async1()
new Promise(resolve => {
  console.log('promise1 start')
  resolve()
  console.log('promise1 end')
}).then(() => {
  console.log('promise2')
})
console.log('script end')
複製代碼

在 Chrome 中的執行順序:

image.png

在 Node.js 10.13.0 中的執行順序:

image.png

在 Node.js 12.18.3 中的執行順序:

image.png

驚不驚喜!意不意外!Chrome(91版本)和 Node.js 12.18.3 的版本表現是一致的,在 Node.js 10.13.0 版本中有些差異,主要是 promise2async1 end 打印順序的不同,也就是說 async/await 在不同版本的 Node.js 處理是不一樣的(Chrome 70 後和 Chrome 70 前的處理也不一樣)。

想要搞清楚其中的區別,可以去 promise, async, await, execution order[2] 一探究竟。

混合版本一(setTimeoutPromise

console.log('script start')
setTimeout(() => {
  console.log('setTimeout1')
  Promise.resolve().then(() => {
    console.log('promise1')
  })
}, 0)
setTimeout(() => {
  console.log('setTimeout2')
  Promise.resolve().then(() => {
    console.log('promise2')
  })
}, 0)
console.log('script end')
複製代碼

在 Chrome 中的執行順序:

image.png

在 Node.js 10.13.0 中的執行順序:

image.png

在 Node.js 12.18.3 中的執行順序:

image.png

這個混合版本主要是看 Node.js 版本 10 前後 Event Loop 的差別。

混合版本二(setTimeoutPromise

setTimeoutPromise

console.log('script start')
Promise.resolve().then(() => {
  console.log('promise1')
  setTimeout(() => {
    console.log('setTimeout1')
  }, 0)
})
setTimeout(() => {
  console.log('setTimeout2')
  Promise.resolve().then(() => {
    console.log('promise2')
  })
}, 0)
console.log('script end')
複製代碼

在 Chrome 中的執行順序:

image.png

在 Node.js 10.13.0 中的執行順序:

image.png

在 Node.js 12.18.3 中的執行順序:

image.png

這個版本是執行完同步代碼(script end)後,這時候第一個 Promisepromise1) 在微任務隊列裏面,第二個 setTimeout 已經加到消息隊列(延遲隊列)尾部了;這時候去執行微任務,即打印 promise1,然後把第一個 setTimeout 加到消息隊列(延遲隊列)尾部,所以會是先打印 setTimeout2promise2 之後再打印 setTimeout1

混合版本三(Promise

宏任務和微任務的組合混合都差不多了,那來看看微任務之間的混合吧!

async function async1() {
  console.log('async1 start');
  Promise.resolve(async2()).then(() => {
    console.log('async1 end');
  })
}
async function async2() {
  console.log('async2');
  Promise.resolve(async3()).then(() => {
    console.log('async2 end');
  })
}
async function async3() {
  console.log('async3');
  Promise.resolve().then(() => {
    console.log('async3 end');
  })
}
console.log('script start');
async1();
new Promise((resolve) =>  {
  console.log('promise1');
  resolve();
}).then(() => {
  console.log('promise2');
});
console.log('script end');
複製代碼

在 Chrome 中的執行順序:

image.png

在 Node.js 10.13.0 中的執行順序:

image.png

這個也容易理解,按照調用棧的出入棧順序,先執行的是同步代碼,執行到 async3 的時候才把 async3 end 加入微任務隊列,之後 async3() 函數出棧,回到 async2,把 async2 end 加入微任務隊列,後面的同理,於是就有了這個打印順序。

混合版本四(Promiseasync/await

async function async1() {
  console.log('async1 start');
  await async2();
  console.log('async1 end');
}
async function async2() {
  console.log('async2');
  await async3()
  console.log('async2 end')
}
async function async3() {
  await console.log('async3');
  console.log('async3 end')
}
console.log('script start');
async1();
new Promise((resolve) => {
  console.log('promise1');
  resolve();
}).then(() => {
  console.log('promise2');
});
console.log('script end');
複製代碼

在 Chrome 中的執行順序:

image.png

在 Node.js 10.13.0 中的執行順序:

image.png

版本四和版本三的區別是將 async 函數裏面的 Promise 換成了 await,而且版本三中的 async 函數裏面沒有 await,都可以把 function 前面的 async 標記拿掉的,不要被迷惑了哦。

在同步代碼執行到 script end 之前我想都沒什麼問題,async 裏面都是有 await 的,所以在後面的代碼可以理解爲如下形式:

Promise.resolve().then(() => {
  console.log('async3 end');
  Promise.resolve().then(() => {
    console.log('async2 end');
    Promise.resolve().then(() => {
      console.log('async1 end');
    })
  })
})
Promise.resolve().then(() => {
  console.log('promise2');
})
複製代碼

混合版本五(Promiseasync/await

function async1() {
  console.log('async1 start');
  Promise.resolve(async2()).then(() => {
    console.log('async1 end');
  })
}
function async2() {
  console.log('async2');
  Promise.resolve(async3()).then(() => {
    console.log('async2 end');
  })
}
async function async3() {
  await console.log('async3');
  console.log('async3 end');
}
console.log('script start');
async1();
new Promise(function (resolve{
  console.log('promise1');
  resolve();
}).then(function () {
  console.log('promise2');
});
console.log('script end');
複製代碼

在 Chrome 中的執行順序:

image.png

在 Node.js 10.13.0 中的執行順序:

image.png

通過前面版本四的考驗,這裏應該也難不倒你,這裏只有 async3 裏面有一個 await,執行到 script end 之前都一樣,到了 async3 end 後面的代碼可以轉化爲以下形式思考:

Promise.resolve().then(() => {
  console.log('async3 end');
  Promise.resolve().then(() => {
    console.log('async2 end');
  })
})
Promise.resolve().then(() => {
  console.log('async1 end');
})
Promise.resolve().then(() => {
  console.log('promise2');
})
複製代碼

總結

紙上得來終覺淺,絕知此事要躬行。

關於本文

來源:起風了Q

https://juejin.cn/post/6979244116199047204


1. JavaScript 重溫系列(22篇全)
2. ECMAScript 重溫系列(10篇全)
3. JavaScript設計模式 重溫系列(9篇全)
4.  正則 / 框架 / 算法等 重溫系列(16篇全)
5.  Webpack4 入門(上) ||  Webpack4 入門(下)
6.  MobX 入門(上)  ||   MobX 入門(下)
7. 120 +篇原創系列彙總

回覆“加羣”與大佬們一起交流學習~

點擊“閱讀原文”查看 120+ 篇原創文章

本文分享自微信公衆號 - 前端自習課(FE-study)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章