偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

一次弄懂 Event Loop(徹底解決此類面試問(wèn)題)

開發(fā)
Event Loop即事件循環(huán),是指瀏覽器或 Node 的一種解決javaScript單線程運(yùn)行時(shí)不會(huì)阻塞的一種機(jī)制,也就是我們經(jīng)常使用異步的原理。

[[344363]]

 為啥要弄懂 Event Loop

  • 是要增加自己技術(shù)的深度,也就是懂得 JavaScript 的運(yùn)行機(jī)制。
  • 現(xiàn)在在前端領(lǐng)域各種技術(shù)層出不窮,掌握底層原理,可以讓自己以不變,應(yīng)萬(wàn)變。
  • 應(yīng)對(duì)各大互聯(lián)網(wǎng)公司的面試,懂其原理,題目任其發(fā)揮。

堆,棧、隊(duì)列

堆(Heap)
堆是一種數(shù)據(jù)結(jié)構(gòu),是利用完全二叉樹維護(hù)的一組數(shù)據(jù),堆分為兩種,一種為最大堆,一種為最小堆,將根節(jié)點(diǎn)最大的堆叫做最大堆或大根堆,根節(jié)點(diǎn)最小的堆叫做最小堆或小根堆。堆是線性數(shù)據(jù)結(jié)構(gòu),相當(dāng)于一維數(shù)組,有唯一后繼。

如最大堆

棧(Stack)
棧在計(jì)算機(jī)科學(xué)中是限定僅在表尾進(jìn)行插入或刪除操作的線性表。棧是一種數(shù)據(jù)結(jié)構(gòu),它按照后進(jìn)先出的原則存儲(chǔ)數(shù)據(jù),先進(jìn)入的數(shù)據(jù)被壓入棧底,最后的數(shù)據(jù)在棧頂,需要讀數(shù)據(jù)的時(shí)候從棧頂開始彈出數(shù)據(jù)。

棧是只能在某一端插入和刪除的特殊線性表。

隊(duì)列(Queue)
特殊之處在于它只允許在表的前端(front)進(jìn)行刪除操作,而在表的后端(rear)進(jìn)行插入操作,和棧一樣,隊(duì)列是一種操作受限制的線性表。

進(jìn)行插入操作的端稱為隊(duì)尾,進(jìn)行刪除操作的端稱為隊(duì)頭。隊(duì)列中沒(méi)有元素時(shí),稱為空隊(duì)列。

隊(duì)列的數(shù)據(jù)元素又稱為隊(duì)列元素。在隊(duì)列中插入一個(gè)隊(duì)列元素稱為入隊(duì),從隊(duì)列中刪除一個(gè)隊(duì)列元素稱為出隊(duì)。因?yàn)殛?duì)列只允許在一端插入,在另一端刪除,所以只有最早進(jìn)入隊(duì)列的元素才能最先從隊(duì)列中刪除,故隊(duì)列又稱為先進(jìn)先出(FIFO—first in first out)

Event Loop
在 JavaScript 中,任務(wù)被分為兩種,一種宏任務(wù)(MacroTask)也叫 Task,一種叫微任務(wù)(MicroTask)。

MacroTask(宏任務(wù))
script 全部代碼、setTimeout、setInterval、setImmediate(瀏覽器暫時(shí)不支持,只有 IE10 支持,具體可見(jiàn) MDN)、I/O、UI Rendering。

MicroTask(微任務(wù))
Process.nextTick(Node 獨(dú)有)、Promise、Object.observe(廢棄)、MutationObserver(具體使用方式查看這里[1])

瀏覽器中的 Event Loop[2]
Javascript 有一個(gè) main thread 主線程和 call-stack 調(diào)用棧(執(zhí)行棧),所有的任務(wù)都會(huì)被放到調(diào)用棧等待主線程執(zhí)行。

JS 調(diào)用棧
JS 調(diào)用棧采用的是后進(jìn)先出的規(guī)則,當(dāng)函數(shù)執(zhí)行的時(shí)候,會(huì)被添加到棧的頂部,當(dāng)執(zhí)行棧執(zhí)行完成后,就會(huì)從棧頂移出,直到棧內(nèi)被清空。

同步任務(wù)和異步任務(wù)
Javascript 單線程任務(wù)被分為同步任務(wù)和異步任務(wù),同步任務(wù)會(huì)在調(diào)用棧中按照順序等待主線程依次執(zhí)行,異步任務(wù)會(huì)在異步任務(wù)有了結(jié)果后,將注冊(cè)的回調(diào)函數(shù)放入任務(wù)隊(duì)列中等待主線程空閑的時(shí)候(調(diào)用棧被清空),被讀取到棧內(nèi)等待主線程的執(zhí)行。

任務(wù)隊(duì)列 Task Queue,即隊(duì)列,是一種先進(jìn)先出的一種數(shù)據(jù)結(jié)構(gòu)。

事件循環(huán)的進(jìn)程模型[3]

  • 選擇當(dāng)前要執(zhí)行的任務(wù)隊(duì)列,選擇任務(wù)隊(duì)列中最先進(jìn)入的任務(wù),如果任務(wù)隊(duì)列為空即 null,則執(zhí)行跳轉(zhuǎn)到微任務(wù)(MicroTask)的執(zhí)行步驟。
  • 將事件循環(huán)中的任務(wù)設(shè)置為已選擇任務(wù)。
  • 執(zhí)行任務(wù)。
  • 將事件循環(huán)中當(dāng)前運(yùn)行任務(wù)設(shè)置為 null。
  • 將已經(jīng)運(yùn)行完成的任務(wù)從任務(wù)隊(duì)列中刪除。
  • microtasks 步驟:進(jìn)入 microtask 檢查點(diǎn)。
  • 更新界面渲染。
  • 返回第一步。

執(zhí)行進(jìn)入 microtask 檢查點(diǎn)時(shí),用戶代理會(huì)執(zhí)行以下步驟:

  • 設(shè)置 microtask 檢查點(diǎn)標(biāo)志為 true。
  • 當(dāng)事件循環(huán) microtask 執(zhí)行不為空時(shí):選擇一個(gè)最先進(jìn)入的 microtask 隊(duì)列的 microtask,將事件循環(huán)的 microtask 設(shè)置為已選擇的 microtask,運(yùn)行 microtask,將已經(jīng)執(zhí)行完成的 microtask 為 null,移出 microtask 中的 microtask。
  • 清理 IndexDB 事務(wù)
  • 設(shè)置進(jìn)入 microtask 檢查點(diǎn)的標(biāo)志為 false。

上述可能不太好理解,下圖是我做的一張圖片。

執(zhí)行棧在執(zhí)行完同步任務(wù)后,查看執(zhí)行棧是否為空,如果執(zhí)行棧為空,就會(huì)去檢查微任務(wù)(microTask) 隊(duì)列是否為空,如果為空的話,就執(zhí)行 Task(宏任務(wù)),否則就一次性執(zhí)行完所有微任務(wù)。

每次單個(gè)宏任務(wù)執(zhí)行完畢后,檢查微任務(wù)(microTask)隊(duì)列是否為空,如果不為空的話,會(huì)按照先入先出的規(guī)則全部執(zhí)行完微任務(wù)(microTask)后,設(shè)置微任務(wù)(microTask)隊(duì)列為 null,然后再執(zhí)行宏任務(wù),如此循環(huán)。

舉個(gè)例子

  1. console.log('script start'); 
  2.  
  3. setTimeout(function() { 
  4. console.log('setTimeout'); 
  5. }, 0); 
  6.  
  7. Promise.resolve().then(function() { 
  8. console.log('promise1'); 
  9. }).then(function() { 
  10. console.log('promise2'); 
  11. }); 
  12. console.log('script end'); 

首先我們劃分幾個(gè)分類:

第一次執(zhí)行:

  1. Tasks:run script、 setTimeout callback 
  2.  
  3. Microtasks:Promise then 
  4.  
  5. JS stack: script 
  6. Log: script start、script end。 

執(zhí)行同步代碼,將宏任務(wù)(Tasks)和微任務(wù)(Microtasks)劃分到各自隊(duì)列中。

第二次執(zhí)行:

  1. Tasks:run script、 setTimeout callback 
  2.  
  3. Microtasks:Promise2 then 
  4.  
  5. JS stack: Promise2 callback 
  6. Log: script start、script end、promise1、promise2 

執(zhí)行宏任務(wù)后,檢測(cè)到微任務(wù)(Microtasks)隊(duì)列中不為空,執(zhí)行 Promise1,執(zhí)行完成 Promise1 后,調(diào)用 Promise2.then,放入微任務(wù)(Microtasks)隊(duì)列中,再執(zhí)行 Promise2.then。

第三次執(zhí)行:

  1. Tasks:setTimeout callback 
  2.  
  3. Microtasks: 
  4.  
  5. JS stack: setTimeout callback 
  6. Log: script start、script end、promise1、promise2、setTimeout 

當(dāng)微任務(wù)(Microtasks)隊(duì)列中為空時(shí),執(zhí)行宏任務(wù)(Tasks),執(zhí)行 setTimeout callback,打印日志。

第四次執(zhí)行:

  1. Tasks:setTimeout callback 
  2.  
  3. Microtasks: 
  4.  
  5. JS stack: 
  6. Log: script start、script end、promise1、promise2、setTimeout 

清空 Tasks 隊(duì)列和 JS stack。

以上執(zhí)行幀動(dòng)畫可以查看 Tasks, microtasks, queues and schedules[4]

或許這張圖也更好理解些。

再舉個(gè)例子

  1. console.log('script start'
  2.  
  3. async function async1() { 
  4. await async2() 
  5. console.log('async1 end'
  6. async function async2() { 
  7. console.log('async2 end'
  8. async1() 
  9.  
  10. setTimeout(function() { 
  11. console.log('setTimeout'
  12. }, 0) 
  13.  
  14. new Promise(resolve => { 
  15. console.log('Promise'
  16. resolve() 
  17. }) 
  18. .then(function() { 
  19. console.log('promise1'
  20. }) 
  21. .then(function() { 
  22. console.log('promise2'
  23. }) 
  24.  
  25. console.log('script end'

這里需要先理解 async/await。

async/await 在底層轉(zhuǎn)換成了 promise 和 then 回調(diào)函數(shù)。也就是說(shuō),這是 promise 的語(yǔ)法糖。每次我們使用 await, 解釋器都創(chuàng)建一個(gè) promise 對(duì)象,然后把剩下的async函數(shù)中的操作放到 then 回調(diào)函數(shù)中。async/await 的實(shí)現(xiàn),離不開 Promise。從字面意思來(lái)理解,async 是“異步”的簡(jiǎn)寫,而 await 是 async wait 的簡(jiǎn)寫可以認(rèn)為是等待異步方法執(zhí)行完成。

關(guān)于 73 以下版本和 73 版本的區(qū)別
在老版本版本以下,先執(zhí)行 promise1 和 promise2,再執(zhí)行 async1。在 73 版本,先執(zhí)行 async1 再執(zhí)行promise1和 promise2。主要原因是因?yàn)樵诠雀?金絲雀)73 版本中更改了規(guī)范,如下圖所示:

區(qū)別在于 RESOLVE(thenable)和之間的區(qū)別 Promise.resolve(thenable)。

在老版本中

  • 首先,傳遞給 await 的值被包裹在一個(gè) Promise 中。然后,處理程序附加到這個(gè)包裝的 Promise,以便在 Promise 變?yōu)?fulfilled 后恢復(fù)該函數(shù),并且暫停執(zhí)行異步函數(shù),一旦 promise 變?yōu)?fulfilled,恢復(fù)異步函數(shù)的執(zhí)行。
  • 每個(gè) await 引擎必須創(chuàng)建兩個(gè)額外的 Promise(即使右側(cè)已經(jīng)是一個(gè) Promise)并且它需要至少三個(gè) microtask 隊(duì)列 ticks(tick 為系統(tǒng)的相對(duì)時(shí)間單位,也被稱為系統(tǒng)的時(shí)基,來(lái)源于定時(shí)器的周期性中斷(輸出脈沖),一次中斷表示一個(gè) tick,也被稱做一個(gè)“時(shí)鐘滴答”、時(shí)標(biāo)。)。

引用賀老師知乎上的一個(gè)例子

  1. async function f() { 
  2. await p 
  3. console.log('ok'

簡(jiǎn)化理解為:

  1. function f() { 
  2. return RESOLVE(p).then(() => { 
  3. console.log('ok'
  4. }) 
  • 如果 RESOLVE(p) 對(duì)于 p 為 promise 直接返回 p 的話,那么 p 的 then 方法就會(huì)被馬上調(diào)用,其回調(diào)就立即進(jìn)入 job 隊(duì)列。
  • 而如果 RESOLVE(p) 嚴(yán)格按照標(biāo)準(zhǔn),應(yīng)該是產(chǎn)生一個(gè)新的 promise,盡管該 promise 確定會(huì) resolve 為 p,但這個(gè)過(guò)程本身是異步的,也就是現(xiàn)在進(jìn)入 job 隊(duì)列的是新 promise 的 resolve 過(guò)程,所以該 promise 的 then 不會(huì)被立即調(diào)用,而要等到當(dāng)前 job 隊(duì)列執(zhí)行到前述 resolve 過(guò)程才會(huì)被調(diào)用,然后其回調(diào)(也就是繼續(xù) await 之后的語(yǔ)句)才加入 job 隊(duì)列,所以時(shí)序上就晚了。

谷歌(金絲雀)73 版本中

  • 使用對(duì) PromiseResolve 的調(diào)用來(lái)更改 await 的語(yǔ)義,以減少在公共 awaitPromise 情況下的轉(zhuǎn)換次數(shù)。
  • 如果傳遞給 await 的值已經(jīng)是一個(gè) Promise,那么這種優(yōu)化避免了再次創(chuàng)建 Promise 包裝器,在這種情況下,我們從最少三個(gè) microtick 到只有一個(gè) microtick。

詳細(xì)過(guò)程:
73 以下版本
首先,打印 script start,調(diào)用 async1()時(shí),返回一個(gè) Promise,所以打印出來(lái) async2 end。每個(gè) await,會(huì)新產(chǎn)生一個(gè) promise,但這個(gè)過(guò)程本身是異步的,所以該 await 后面不會(huì)立即調(diào)用。繼續(xù)執(zhí)行同步代碼,打印 Promise 和 script end,將 then 函數(shù)放入微任務(wù)隊(duì)列中等待執(zhí)行。同步執(zhí)行完成之后,檢查微任務(wù)隊(duì)列是否為 null,然后按照先入先出規(guī)則,依次執(zhí)行。然后先執(zhí)行打印 promise1,此時(shí) then 的回調(diào)函數(shù)返回 undefinde,此時(shí)又有 then 的鏈?zhǔn)秸{(diào)用,又放入微任務(wù)隊(duì)列中,再次打印 promise2。再回到 await 的位置執(zhí)行返回的 Promise 的 resolve 函數(shù),這又會(huì)把 resolve 丟到微任務(wù)隊(duì)列中,打印 async1 end。當(dāng)微任務(wù)隊(duì)列為空時(shí),執(zhí)行宏任務(wù),打印 setTimeout。

谷歌(金絲雀 73 版本)
如果傳遞給 await 的值已經(jīng)是一個(gè) Promise,那么這種優(yōu)化避免了再次創(chuàng)建 Promise 包裝器,在這種情況下,我們從最少三個(gè) microtick 到只有一個(gè) microtick。引擎不再需要為 await 創(chuàng)造 throwaway Promise - 在絕大部分時(shí)間?,F(xiàn)在 promise 指向了同一個(gè) Promise,所以這個(gè)步驟什么也不需要做。然后引擎繼續(xù)像以前一樣,創(chuàng)建 throwaway Promise,安排 PromiseReactionJob 在 microtask 隊(duì)列的下一個(gè) tick 上恢復(fù)異步函數(shù),暫停執(zhí)行該函數(shù),然后返回給調(diào)用者。具體詳情查看(這里[5])。

NodeJS 的 Event Loop[6]

Node 中的 Event Loop 是基于 libuv 實(shí)現(xiàn)的,而 libuv 是 Node 的新跨平臺(tái)抽象層,libuv 使用異步,事件驅(qū)動(dòng)的編程方式,核心是提供 i/o 的事件循環(huán)和異步回調(diào)。libuv 的 API 包含有時(shí)間,非阻塞的網(wǎng)絡(luò),異步文件操作,子進(jìn)程等等。Event Loop 就是在 libuv 中實(shí)現(xiàn)的。

Node[7] 的 Event loop 一共分為 6 個(gè)階段,每個(gè)細(xì)節(jié)具體如下:

  • timers: 執(zhí)行 setTimeout 和 setInterval 中到期的 callback。
  • pending callback: 上一輪循環(huán)中少數(shù)的 callback 會(huì)放在這一階段執(zhí)行。
  • idle, prepare: 僅在內(nèi)部使用。
  • poll: 最重要的階段,執(zhí)行 pending callback,在適當(dāng)?shù)那闆r下回阻塞在這個(gè)階段。
  • check: 執(zhí)行 setImmediate(setImmediate()是將事件插入到事件隊(duì)列尾部,主線程和事件隊(duì)列的函數(shù)執(zhí)行完成之后立即執(zhí)行 setImmediate 指定的回調(diào)函數(shù))的 callback。
  • close callbacks: 執(zhí)行 close 事件的 callback,例如 socket.on('close'[,fn])或者 http.server.on('close, fn)。具體細(xì)節(jié)如下:

timers
執(zhí)行 setTimeout 和 setInterval 中到期的 callback,執(zhí)行這兩者回調(diào)需要設(shè)置一個(gè)毫秒數(shù),理論上來(lái)說(shuō),應(yīng)該是時(shí)間一到就立即執(zhí)行 callback 回調(diào),但是由于 system 的調(diào)度可能會(huì)延時(shí),達(dá)不到預(yù)期時(shí)間。以下是官網(wǎng)文檔[8]解釋的例子:

  1. const fs = require('fs'); 
  2.  
  3. function someAsyncOperation(callback) { 
  4. // Assume this takes 95ms to complete 
  5. fs.readFile('/path/to/file', callback); 
  6.  
  7. const timeoutScheduled = Date.now(); 
  8.  
  9. setTimeout(() => { 
  10. const delay = Date.now() - timeoutScheduled; 
  11.  
  12. console.log(`${delay}ms have passed since I was scheduled`); 
  13. }, 100); 
  14.  
  15. // do someAsyncOperation which takes 95 ms to complete 
  16. someAsyncOperation(() => { 
  17. const startCallback = Date.now(); 
  18.  
  19. // do something that will take 10ms... 
  20. while (Date.now() - startCallback < 10) { 
  21. // do nothing 
  22. }); 

當(dāng)進(jìn)入事件循環(huán)時(shí),它有一個(gè)空隊(duì)列(fs.readFile()尚未完成),因此定時(shí)器將等待剩余毫秒數(shù),當(dāng)?shù)竭_(dá) 95ms 時(shí),fs.readFile()完成讀取文件并且其完成需要 10 毫秒的回調(diào)被添加到輪詢隊(duì)列并執(zhí)行。當(dāng)回調(diào)結(jié)束時(shí),隊(duì)列中不再有回調(diào),因此事件循環(huán)將看到已達(dá)到最快定時(shí)器的閾值,然后回到 timers 階段以執(zhí)行定時(shí)器的回調(diào)。

在此示例中,您將看到正在調(diào)度的計(jì)時(shí)器與正在執(zhí)行的回調(diào)之間的總延遲將為 105 毫秒。

以下是我測(cè)試時(shí)間:

pending callbacks
此階段執(zhí)行某些系統(tǒng)操作(例如 TCP 錯(cuò)誤類型)的回調(diào)。例如,如果 TCP socket ECONNREFUSED 在嘗試 connect 時(shí) receives,則某些* nix 系統(tǒng)希望等待報(bào)告錯(cuò)誤。這將在 pending callbacks 階段執(zhí)行。

poll
該 poll 階段有兩個(gè)主要功能:

  • 執(zhí)行 I/O 回調(diào)。
  • 處理輪詢隊(duì)列中的事件。

當(dāng)事件循環(huán)進(jìn)入 poll 階段并且在 timers 中沒(méi)有可以執(zhí)行定時(shí)器時(shí),將發(fā)生以下兩種情況之一

  • 如果 poll 隊(duì)列不為空,則事件循環(huán)將遍歷其同步執(zhí)行它們的 callback 隊(duì)列,直到隊(duì)列為空,或者達(dá)到 system-dependent(系統(tǒng)相關(guān)限制)。
  • 如果 poll 隊(duì)列為空,則會(huì)發(fā)生以下兩種情況之一
  • 如果有 setImmediate()回調(diào)需要執(zhí)行,則會(huì)立即停止執(zhí)行 poll 階段并進(jìn)入執(zhí)行 check 階段以執(zhí)行回調(diào)。
  • 如果沒(méi)有 setImmediate()回到需要執(zhí)行,poll 階段將等待 callback 被添加到隊(duì)列中,然后立即執(zhí)行。

當(dāng)然設(shè)定了 timer 的話且 poll 隊(duì)列為空,則會(huì)判斷是否有 timer 超時(shí),如果有的話會(huì)回到 timer 階段執(zhí)行回調(diào)。
check
此階段允許人員在 poll 階段完成后立即執(zhí)行回調(diào)。如果 poll 階段閑置并且 script 已排隊(duì) setImmediate(),則事件循環(huán)到達(dá) check 階段執(zhí)行而不是繼續(xù)等待。

setImmediate()實(shí)際上是一個(gè)特殊的計(jì)時(shí)器,它在事件循環(huán)的一個(gè)單獨(dú)階段運(yùn)行。它使用 libuv API 來(lái)調(diào)度在 poll 階段完成后執(zhí)行的回調(diào)。

通常,當(dāng)代碼被執(zhí)行時(shí),事件循環(huán)最終將達(dá)到poll階段,它將等待傳入連接,請(qǐng)求等。但是,如果已經(jīng)調(diào)度了回調(diào) setImmediate(),并且輪詢階段變?yōu)榭臻e,則它將結(jié)束并且到達(dá)check階段,而不是等待 poll 事件。

  1. console.log('start'
  2. setTimeout(() => { 
  3. console.log('timer1'
  4. Promise.resolve().then(function() { 
  5. console.log('promise1'
  6. }) 
  7. }, 0) 
  8. setTimeout(() => { 
  9. console.log('timer2'
  10. Promise.resolve().then(function() { 
  11. console.log('promise2'
  12. }) 
  13. }, 0) 
  14. Promise.resolve().then(function() { 
  15. console.log('promise3'
  16. }) 
  17. console.log('end'

如果 node 版本為 v11.x, 其結(jié)果與瀏覽器一致。

  1. start 
  2. end 
  3. promise3 
  4. timer1 
  5. promise1 
  6. timer2 
  7. promise2 

具體詳情可以查看《又被 node 的 eventloop 坑了,這次是 node 的鍋》[9]。

如果 v10 版本上述結(jié)果存在兩種情況:

  • 如果 time2 定時(shí)器已經(jīng)在執(zhí)行隊(duì)列中了
  1. start 
  2. end 
  3. promise3 
  4. timer1 
  5. timer2 
  6. promise1 
  7. promise2 
  • 如果 time2 定時(shí)器沒(méi)有在執(zhí)行對(duì)列中,執(zhí)行結(jié)果為
  1. start 
  2. end 
  3. promise3 
  4. timer1 
  5. promise1 
  6. timer2 
  7. promise2 

具體情況可以參考 poll 階段的兩種情況。

從下圖可能更好理解:

setImmediate() 的 setTimeout()的區(qū)別
setImmediate 和 setTimeout()是相似的,但根據(jù)它們被調(diào)用的時(shí)間以不同的方式表現(xiàn)。

  • setImmediate()設(shè)計(jì)用于在當(dāng)前 poll 階段完成后check階段執(zhí)行腳本 。
  • setTimeout() 安排在經(jīng)過(guò)最?。╩s)后運(yùn)行的腳本,在 timers 階段執(zhí)行。舉個(gè)例子
  1. setTimeout(() => { 
  2. console.log('timeout'); 
  3. }, 0); 
  4.  
  5. setImmediate(() => { 
  6. console.log('immediate'); 
  7. }); 

執(zhí)行定時(shí)器的順序?qū)⒏鶕?jù)調(diào)用它們的上下文而有所不同。如果從主模塊中調(diào)用兩者,那么時(shí)間將受到進(jìn)程性能的限制。

其結(jié)果也不一致

如果在 I / O 周期內(nèi)移動(dòng)兩個(gè)調(diào)用,則始終首先執(zhí)行立即回調(diào):

  1. const fs = require('fs'); 
  2.  
  3. fs.readFile(\_\_filename, () => { 
  4. setTimeout(() => { 
  5. console.log('timeout'); 
  6. }, 0); 
  7. setImmediate(() => { 
  8. console.log('immediate'); 
  9. }); 
  10. }); 

其結(jié)果可以確定一定是 immediate => timeout。主要原因是在 I/O 階段讀取文件后,事件循環(huán)會(huì)先進(jìn)入 poll 階段,發(fā)現(xiàn)有 setImmediate 需要執(zhí)行,會(huì)立即進(jìn)入 check 階段執(zhí)行 setImmediate 的回調(diào)。

然后再進(jìn)入 timers 階段,執(zhí)行 setTimeout,打印 timeout。

  1. ┌───────────────────────────┐ 
  2. ┌─>│ timers │ 
  3. │ └─────────────┬─────────────┘ 
  4. │ ┌─────────────┴─────────────┐ 
  5. │ │ pending callbacks │ 
  6. │ └─────────────┬─────────────┘ 
  7. │ ┌─────────────┴─────────────┐ 
  8. │ │ idle, prepare │ 
  9. │ └─────────────┬─────────────┘ ┌───────────────┐ 
  10. │ ┌─────────────┴─────────────┐ │ incoming: │ 
  11. │ │ poll │<─────┤ connections, │ 
  12. │ └─────────────┬─────────────┘ │ data, etc. │ 
  13. │ ┌─────────────┴─────────────┐ └───────────────┘ 
  14. │ │ check │ 
  15. │ └─────────────┬─────────────┘ 
  16. │ ┌─────────────┴─────────────┐ 
  17. └──┤ close callbacks │ 
  18. └───────────────────────────┘ 

Process.nextTick()
process.nextTick()雖然它是異步 API 的一部分,但未在圖中顯示。這是因?yàn)?process.nextTick()從技術(shù)上講,它不是事件循環(huán)的一部分。

process.nextTick()方法將 callback 添加到 next tick 隊(duì)列。一旦當(dāng)前事件輪詢隊(duì)列的任務(wù)全部完成,在 next tick 隊(duì)列中的所有 callbacks 會(huì)被依次調(diào)用。換種理解方式:

當(dāng)每個(gè)階段完成后,如果存在nextTick隊(duì)列,就會(huì)清空隊(duì)列中的所有回調(diào)函數(shù),并且優(yōu)先于其他 microtask 執(zhí)行。例子

  1. let bar; 
  2.  
  3. setTimeout(() => { 
  4. console.log('setTimeout'); 
  5. }, 0) 
  6.  
  7. setImmediate(() => { 
  8. console.log('setImmediate'); 
  9. }) 
  10. function someAsyncApiCall(callback) { 
  11. process.nextTick(callback); 
  12.  
  13. someAsyncApiCall(() => { 
  14. console.log('bar', bar); // 1 
  15. }); 
  16.  
  17. bar = 1; 

在 NodeV10 中上述代碼執(zhí)行可能有兩種答案,一種為:

  1. bar 1 
  2. setTimeout 
  3. setImmediate 

另一種為:

  1. bar 1 
  2. setImmediate 
  3. setTimeout 

最后
感謝@Dante_Hu 提出這個(gè)問(wèn)題 await 的問(wèn)題,文章已經(jīng)修正。修改了 node 端執(zhí)行結(jié)果。V10 和 V11 的區(qū)別。

參考資料

 

[1]

MutationObserver: https://javascript.ruanyifeng.com/dom/mutationobserver.html

[2]

jS事件循環(huán)機(jī)制: https://segmentfault.com/a/1190000015559210

[3]

事件循環(huán)的進(jìn)程模型: https://segmentfault.com/a/1190000010622146

[4]

Tasks, microtasks, queues and schedules: https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/

[5]

Promise : https://v8.js.cn/blog/fast-async/

[6]

瀏覽器與Node的事件循環(huán)(Event Loop)有何區(qū)別?: https://juejin.im/post/6844903761949753352

[7]

不要混淆nodejs和瀏覽器中的event loop: https://cnodejs.org/topic/5a9108d78d6e16e56bb80882

[8]

The Node.js Event Loop, Timers, and process.nextTick(): https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/

[9]

《又被 node 的 eventloop 坑了,這次是 node 的鍋》: https://juejin.im/post/6844903761979113479

 

 

 

責(zé)任編輯:姜華 來(lái)源: 小丑的小屋
相關(guān)推薦

2024-05-20 00:00:00

代碼主線程

2025-04-09 10:36:32

2024-10-09 12:05:27

2019-11-08 16:05:54

Promise前端鏈?zhǔn)秸{(diào)用

2019-09-12 09:40:34

秒殺系統(tǒng)高并發(fā)

2018-08-07 14:45:52

編程語(yǔ)言JavaScripthtml

2021-12-03 12:15:01

QT中文亂碼Windows

2009-11-27 10:31:02

GPRS路由

2023-02-27 08:08:54

Pulsar源碼重復(fù)消費(fèi)

2025-03-03 00:13:50

2010-01-11 18:05:24

VB.NET窗體繼承

2010-01-04 15:05:53

2023-11-28 08:36:16

Spring中Body讀取

2009-12-25 09:39:08

ADSL MODEM

2025-06-17 06:40:45

DockerDocker鏡像

2010-01-14 10:19:05

2009-11-24 19:50:10

2009-12-03 18:45:41

2022-10-08 23:55:58

iOS蘋果開發(fā)

2025-02-11 00:00:00

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)