欧美亚洲中文,在线国自产视频,欧洲一区在线观看视频,亚洲综合中文字幕在线观看

      1. <dfn id="rfwes"></dfn>
          <object id="rfwes"></object>
        1. 站長資訊網(wǎng)
          最全最豐富的資訊網(wǎng)站

          圖文結(jié)合帶你搞懂Nodejs中的事件循環(huán)

          本篇文章通過圖文結(jié)合的形式來帶大家搞懂Nodejs中的事件循環(huán),希望對大家有所幫助!

          圖文結(jié)合帶你搞懂Nodejs中的事件循環(huán)

          以下全文7000字,請在你思路清晰、精力充沛的時刻觀看。保證你理解后很長時間忘不掉?!就扑]學習:《nodejs 教程》】

          圖文結(jié)合帶你搞懂Nodejs中的事件循環(huán)

          Node事件循環(huán)

          Node底層使用的語言libuv,是一個c++語言。他用來操作底層的操作系統(tǒng),封裝了操作系統(tǒng)的接口。Node的事件循環(huán)也是用libuv來寫的,所以Node生命周期和瀏覽器的還是有區(qū)別的。

          因為Node和操作系統(tǒng)打交道,所以事件循環(huán)比較復(fù)雜,也有一些自己特有的API。
          事件循環(huán)在不同的操作系統(tǒng)里有一些細微的差異。這將涉及到操作系統(tǒng)的知識,暫時不表。 本次只介紹JS主線程中,Node的運作流程。Node的其他線程暫時也不擴展。

          事件循環(huán)圖

          說好的一張圖,也不賣關(guān)子。下邊這張圖搞清楚了,事件循環(huán)就學會了。

          圖文結(jié)合帶你搞懂Nodejs中的事件循環(huán)

          事件循環(huán)圖

          圖文結(jié)合帶你搞懂Nodejs中的事件循環(huán)

          事件循環(huán)圖-結(jié)構(gòu)

          為了讓大家先有個大局觀,先貼一張目錄結(jié)構(gòu)圖在前邊:

          圖文結(jié)合帶你搞懂Nodejs中的事件循環(huán)

          目錄

          接下來詳細展開說說

          主線程

          圖文結(jié)合帶你搞懂Nodejs中的事件循環(huán)

          主線程

          上圖中,幾個色塊的含義:

          • main:啟動入口文件,運行主函數(shù)
          • event loop:檢查是否要進入事件循環(huán)
            • 檢查其他線程里是否還有待處理事項
            • 檢查其他任務(wù)是否還在進行中(比如計時器、文件讀取操作等任務(wù)是否完成)
            • 有以上情況,進入事件循環(huán),運行其他任務(wù)
              事件循環(huán)的過程:沿著從timers到close callbacks這個流程,走一圈。到event loop看是否結(jié)束,沒結(jié)束再走一圈。
          • over:所有的事情都完畢,結(jié)束

          事件循環(huán) 圈

          圖文結(jié)合帶你搞懂Nodejs中的事件循環(huán)

          事件循環(huán) 圈

          圖中灰色的圈跟操作系統(tǒng)有關(guān)系,不是本章解析重點。重點關(guān)注黃色、橙色的圈還有中間橘黃的方框。

          我們把每一圈的事件循環(huán)叫做「一次循環(huán)」、又叫「一次輪詢」、又叫「一次Tick」。

          一次循環(huán)要經(jīng)過六個階段:

          • timers:計時器(setTimeout、setInterval等的回調(diào)函數(shù)存放在里邊)

          • pending callback

          • idle prepare

          • poll:輪詢隊列(除timers、check之外的回調(diào)存放在這里)

          • check:檢查階段(使用 setImmediate 的回調(diào)會直接進入這個隊列)

          • close callbacks

          圖文結(jié)合帶你搞懂Nodejs中的事件循環(huán)

          本次我們只關(guān)注上邊標紅的三個重點。

          工作原理

          • 每一個階段都會維護一個事件隊列。可以把每一個圈想象成一個事件隊列。
          • 這就和瀏覽器不一樣了,瀏覽器最多兩個隊列(宏隊列、微隊列)。但是在node里邊有六個隊列
          • 到達一個隊列后,檢查隊列內(nèi)是否有任務(wù)(也就是看下是否有回調(diào)函數(shù))需要執(zhí)行。如果有,就依次執(zhí)行,直到全部執(zhí)行完畢、清空隊列。
          • 如果沒有任務(wù),進入下一個隊列去檢查。直到所有隊列檢查一遍,算一個輪詢。
          • 其中,timers、pending callback、idle prepare等執(zhí)行完畢后,到達poll隊列。

          timers隊列的工作原理

          timers并非真正意義上的隊列,他內(nèi)部存放的是計時器。
          每次到達這個隊列,會檢查計時器線程內(nèi)的所有計時器,計時器線程內(nèi)部多個計時器按照時間順序排序。

          檢查過程:將每一個計時器按順序分別計算一遍,計算該計時器開始計時的時間到當前時間是否滿足計時器的間隔參數(shù)設(shè)定(比如1000ms,計算計時器開始計時到現(xiàn)在是否有1m)。當某個計時器檢查通過,則執(zhí)行其回調(diào)函數(shù)。

          poll隊列的運作方式

          • 如果poll中有回調(diào)函數(shù)需要執(zhí)行,依次執(zhí)行回調(diào),直到清空隊列。
          • 如果poll中沒有回調(diào)函數(shù)需要執(zhí)行,已經(jīng)是空隊列了。則會在這里等待,等待其他隊列中出現(xiàn)回調(diào),
            • 如果其他隊列中出現(xiàn)回調(diào),則從poll向下到over,結(jié)束該階段,進入下一階段。
            • 如果其他隊列也都沒有回調(diào),則持續(xù)在poll隊列等待,直到任何一個隊列出現(xiàn)回調(diào)后再進行工作。(是個小懶蟲的處事方式)

          舉例梳理事件流程

          setTimeout(() => {   console.log('object'); }, 5000) console.log('node');

          以上代碼的事件流程梳理

          • 進入主線程,執(zhí)行setTimeout(),回調(diào)函數(shù)作為異步任務(wù)被放入異步隊列timers隊列中,暫時不執(zhí)行。
          • 繼續(xù)向下,執(zhí)行定時器后邊的console,打印“node”。
          • 判斷是否有事件循環(huán)。是,走一圈輪詢:從timers – pending callback – idle prepare……
          • poll隊列停下循環(huán)并等待。
            • 由于這時候沒到5秒,timers隊列無任務(wù),所以一直在poll隊列卡著,同時輪詢檢查其他隊列是否有任務(wù)。
          • 等5秒到達,setTimeout的回調(diào)塞到timers內(nèi),例行輪詢檢查到timers隊列有任務(wù),則向下走,經(jīng)過check、close callbacks后到達timers。將timers隊列清空。
          • 繼續(xù)輪詢到poll等待,詢問是否還需要event loop,不需要,則到達over結(jié)束。

          要理解這個問題,看下邊的代碼及流程解析:

          setTimeout(function t1() {   console.log('setTimeout'); }, 5000) console.log('node 生命周期');  const http = require('http')  const server = http.createServer(function h1() {   console.log('請求回調(diào)'); });  server.listen(8080)

          代碼分析如下:

          • 照舊,先執(zhí)行主線程,打印“node 生命周期”、引入http后創(chuàng)建http服務(wù)。
          • 然后event loop檢查是否有異步任務(wù),檢查發(fā)現(xiàn)有定時器任務(wù)和請求任務(wù)。所以進入事件循環(huán)。
          • 六個隊列都沒任務(wù),則在poll隊列等待。如下圖:

            圖文結(jié)合帶你搞懂Nodejs中的事件循環(huán)

          • 過了五秒,timers中有了任務(wù),則流程從poll放行向下,經(jīng)過check和close callbacks隊列后,到達event loop。
          • event loop檢查是否有異步任務(wù),檢查發(fā)現(xiàn)有定時器任務(wù)和請求任務(wù)。所以再次進入事件循環(huán)。
          • 到達timers隊列,發(fā)現(xiàn)有回調(diào)函數(shù)任務(wù),則依次執(zhí)行回調(diào),清空timers隊列(當然這里只有一個5秒到達后的回調(diào),所以直接執(zhí)行完了即可),打印出“setTimeout”。如下圖

            圖文結(jié)合帶你搞懂Nodejs中的事件循環(huán)

          • 清空timers隊列后,輪詢繼續(xù)向下到達poll隊列,由于poll隊列現(xiàn)在是空隊列,所以在這里等待。
          • 后來,假設(shè)用戶請求發(fā)來了,h1回調(diào)函數(shù)被放到poll隊列。于是poll中有回調(diào)函數(shù)需要執(zhí)行,依次執(zhí)行回調(diào),直到清空poll隊列。
          • poll隊列清空,此時poll隊列是空隊列,繼續(xù)等待。

            圖文結(jié)合帶你搞懂Nodejs中的事件循環(huán)

          • 由于node線程一直holding在poll隊列,等很長一段時間還是沒有任務(wù)來臨時,會自動斷開等待(不自信表現(xiàn)),向下執(zhí)行輪詢流程,經(jīng)過check、close callbacks后到達event loop
          • 到了event loop后,檢查是否有異步任務(wù),檢查發(fā)現(xiàn)有請求任務(wù)。(此時定時器任務(wù)已經(jīng)執(zhí)行完畢,所以沒有了),則繼續(xù)再次進入事件循環(huán)。
          • 到達poll隊列,再次holding……
          • 再等很長時間沒有任務(wù)來臨,自動斷開到even loop(再補充一點無任務(wù)的循環(huán)情況)
          • 再次回到poll隊列掛起
          • 無限循環(huán)……

          梳理事件循環(huán)流程圖:

          注意:下圖中的“是否有任務(wù)”的說法表示“是否有本隊列的任務(wù)”。

          圖文結(jié)合帶你搞懂Nodejs中的事件循環(huán)

          event loop流程梳理

          再用一個典型的例子驗證下流程:

          const startTime = new Date();  setTimeout(function f1() {   console.log('setTimeout', new Date(), new Date() - startTime); }, 200)  console.log('node 生命周期', startTime);  const fs = require('fs')  fs.readFile('./poll.js', 'utf-8', function fsFunc(err, data) {   const fsTime = new Date()   console.log('fs', fsTime);   while (new Date() - fsTime < 300) {   }   console.log('結(jié)束死循環(huán)', new Date()); });

          連續(xù)運行三遍,打印結(jié)果如下:

          圖文結(jié)合帶你搞懂Nodejs中的事件循環(huán)

          執(zhí)行流程解析:

          • 執(zhí)行全局上下文,打印「node 生命周期 + 時間」

          • 詢問是否有event loop

          • 有,進入timers隊列,檢查沒有計時器(cpu處理速度可以,這時還沒到200ms)

          • 輪詢進入到poll,讀文件還沒讀完(比如此時才用了20ms),因此poll隊列是空的,也沒有任務(wù)回調(diào)

          • 在poll隊列等待……不斷輪詢看有沒有回調(diào)

          • 文件讀完,poll隊列有了fsFunc回調(diào)函數(shù),并且被執(zhí)行,輸出「fs + 時間」

          • 在while死循環(huán)那里卡300毫秒,

          • 死循環(huán)卡到200ms的時候,f1回調(diào)進入timers隊列。但此時poll隊列很忙,占用了線程,不會向下執(zhí)行。

          • 直到300ms后poll隊列清空,輸出「結(jié)束死循環(huán) + 時間」

          • event loop趕緊向下走

          • 再來一輪到timers,執(zhí)行timers隊列里的f1回調(diào)。于是看到「setTimeout + 時間」

          • timers隊列清空,回到poll隊列,沒有任務(wù),等待一會。

          • 等待時間夠長后,向下回到event loop。

          • event loop檢查沒有其他異步任務(wù)了,結(jié)束線程,整個程序over退出。

          check 階段

          檢查階段(使用 setImmediate 的回調(diào)會直接進入這個隊列)

          check隊列的實際工作原理

          真正的隊列,里邊扔的就是待執(zhí)行的回調(diào)函數(shù)的集合。類似[fn,fn]這種形式的。
          每次到達check這個隊列后,立即按順序執(zhí)行回調(diào)函數(shù)即可【類似于[fn1,fn2].forEach((fn)=>fn())的感覺】

          所以說,setImmediate不是一個計時器的概念。

          如果你去面試,涉及到Node環(huán)節(jié),可能會遇到下邊這個問題:setImmediate和setTimeout(0)誰更快。

          setImmediate() 與 setTimeout(0) 的對比

          • setImmediate的回調(diào)是異步的,和setTimeout回調(diào)性質(zhì)一致。
          • setImmediate回調(diào)在check隊列,setTimeout回調(diào)在timers隊列(概念意義,實際在計時器線程,只是setTimeout在timers隊列做檢查調(diào)用而已。詳細看timers的工作原理)。
          • setImmediate函數(shù)調(diào)用后,回調(diào)函數(shù)會立即push到check隊列,并在下次eventloop時被執(zhí)行。setTimeout函數(shù)調(diào)用后,計時器線程增加一個定時器任務(wù),下次eventloop時會在timers階段里檢查判斷定時器任務(wù)是否到達時間,到了則執(zhí)行回調(diào)函數(shù)。
          • 綜上,setImmediate的運算速度比setTimeout(0)的要快,因為setTimeout還需要開計時器線程,并增加計算的開銷。

          二者的效果差不多。但是執(zhí)行順序不定

          觀察以下代碼:

          setTimeout(() => {   console.log('setTimeout'); }, 0);  setImmediate(() => {   console.log('setImmediate'); });

          多次反復(fù)運行,執(zhí)行效果如下:

          圖文結(jié)合帶你搞懂Nodejs中的事件循環(huán)

          順序不定

          可以看到多次運行,兩句console.log打印的順序不定。
          這是因為setTimeout的間隔數(shù)最小填1,雖然下邊代碼填了0。但實際計算機執(zhí)行當1ms算。(這里注意和瀏覽器的計時器區(qū)分。在瀏覽器中,setInterval的最小間隔數(shù)為10ms,小于10ms則會被設(shè)置為10;設(shè)備供電狀態(tài)下,間隔最小為16.6ms。)

          以上代碼,主線程運行的時候,setTimeout函數(shù)調(diào)用,計時器線程增加一個定時器任務(wù)。setImmediate函數(shù)調(diào)用后,其回調(diào)函數(shù)立即push到check隊列。主線程執(zhí)行完畢。

          eventloop判斷時,發(fā)現(xiàn)timers和check隊列有內(nèi)容,進入異步輪詢:

          第一種情況:等到了timers里這段時間,可能還沒有1ms的時間,定時器任務(wù)間隔時間的條件不成立所以timers里還沒有回調(diào)函數(shù)。繼續(xù)向下到了check隊列里,這時候setImmediate的回調(diào)函數(shù)早已等候多時,直接執(zhí)行。而再下次eventloop到達timers隊列,定時器也早已成熟,才會執(zhí)行setTimeout的回調(diào)任務(wù)。于是順序就是「setImmediate -> setTimeout」。

          第二種情況:但也有可能到了timers階段時,超過了1ms。于是計算定時器條件成立,setTimeout的回調(diào)函數(shù)被直接執(zhí)行。eventloop再向下到達check隊列執(zhí)行setImmediate的回調(diào)。最終順序就是「setTimeout -> setImmediate」了。

          所以,只比較這兩個函數(shù)的情況下,二者的執(zhí)行順序最終結(jié)果取決于當下計算機的運行環(huán)境以及運行速度。

          二者時間差距的對比代碼

          ------------------setTimeout測試:------------------- let i = 0; console.time('setTimeout'); function test() {   if (i < 1000) {     setTimeout(test, 0)     i++   } else {     console.timeEnd('setTimeout');   } } test();  ------------------setImmediate測試:------------------- let i = 0; console.time('setImmediate'); function test() {   if (i < 1000) {     setImmediate(test)     i++   } else {     console.timeEnd('setImmediate');   } } test();

          運行觀察時間差距:

          圖文結(jié)合帶你搞懂Nodejs中的事件循環(huán)

          setTimeout與setImmediate時間差距

          可見setTimeout遠比setImmediate耗時多得多
          這是因為setTimeout不僅有主代碼執(zhí)行的時間消耗。還有在timers隊列里,對于計時器線程中各個定時任務(wù)的計算時間。

          結(jié)合poll隊列的面試題(考察timers、poll和check的執(zhí)行順序)

          如果你看懂了上邊的事件循環(huán)圖,下邊這道題難不倒你!

          // 說說下邊代碼的執(zhí)行順序,先打印哪個? const fs = require('fs') fs.readFile('./poll.js', () => {   setTimeout(() => console.log('setTimeout'), 0)   setImmediate(() => console.log('setImmediate')) })

          上邊這種代碼邏輯,不管執(zhí)行多少次,肯定都是先執(zhí)行setImmediate。

          圖文結(jié)合帶你搞懂Nodejs中的事件循環(huán)

          先執(zhí)行setImmediate

          因為fs各個函數(shù)的回調(diào)是放在poll隊列的。當程序holding在poll隊列后,出現(xiàn)回調(diào)立即執(zhí)行。
          回調(diào)內(nèi)執(zhí)行setTimeout和setImmediate的函數(shù)后,check隊列立即增加了回調(diào)。
          回調(diào)執(zhí)行完畢,輪詢檢查其他隊列有內(nèi)容,程序結(jié)束poll隊列的holding向下執(zhí)行。
          check是poll階段的緊接著的下一個。所以在向下的過程中,先執(zhí)行check階段內(nèi)的回調(diào),也就是先打印setImmediate。
          到下一輪循環(huán),到達timers隊列,檢查setTimeout計時器符合條件,則定時器回調(diào)被執(zhí)行。

          nextTick 與 Promise

          說完宏任務(wù),接下來說下微任務(wù)

          • 二者都是「微隊列」,執(zhí)行異步微任務(wù)。
          • 二者不是事件循環(huán)的一部分,程序也不會開啟額外的線程去處理相關(guān)任務(wù)。(理解:promise里發(fā)網(wǎng)絡(luò)請求,那是網(wǎng)絡(luò)請求開的網(wǎng)絡(luò)線程,跟Promise這個微任務(wù)沒關(guān)系)
          • 微隊列設(shè)立的目的就是讓一些任務(wù)「馬上」、「立即」優(yōu)先執(zhí)行。
          • nextTick與Promise比較,nextTick的級別更高。

          nextTick表現(xiàn)形式

          process.nextTick(() => {})

          Promise表現(xiàn)形式

          Promise.resolve().then(() => {})

          如何參與事件循環(huán)?

          事件循環(huán)中,每執(zhí)行一個回調(diào)前,先按序清空一次nextTick和promise。

          // 先思考下列代碼的執(zhí)行順序 setImmediate(() => {   console.log('setImmediate'); });  process.nextTick(() => {   console.log('nextTick 1');   process.nextTick(() => {     console.log('nextTick 2');   }) })  console.log('global');   Promise.resolve().then(() => {   console.log('promise 1');   process.nextTick(() => {     console.log('nextTick in promise');   }) })

          最終順序:

          • global

          • nextTick 1

          • nextTick 2

          • promise 1

          • nextTick in promise

          • setImmediate

          兩個問題:

          基于上邊的說法,有兩個問題待思考和解決:

          • 每走一個異步宏任務(wù)隊列就查一遍nextTick和promise?還是每執(zhí)行完 宏任務(wù)隊列里的一個回調(diào)函數(shù)就查一遍呢?

          • 如果在poll的holding階段,插入一個nextTick或者Promise的回調(diào),會立即停止poll隊列的holding去執(zhí)行回調(diào)嗎?

          圖文結(jié)合帶你搞懂Nodejs中的事件循環(huán)

          上邊兩個問題,看下邊代碼的說法

          setTimeout(() => {   console.log('setTimeout 100');   setTimeout(() => {     console.log('setTimeout 100 - 0');     process.nextTick(() => {       console.log('nextTick in setTimeout 100 - 0');     })   }, 0)   setImmediate(() => {     console.log('setImmediate in setTimeout 100');     process.nextTick(() => {       console.log('nextTick in setImmediate in setTimeout 100');     })   });   process.nextTick(() => {     console.log('nextTick in setTimeout100');   })   Promise.resolve().then(() => {     console.log('promise in setTimeout100');   }) }, 100)  const fs = require('fs') fs.readFile('./1.poll.js', () => {   console.log('poll 1');   process.nextTick(() => {     console.log('nextTick in poll ======');   }) })  setTimeout(() => {   console.log('setTimeout 0');   process.nextTick(() => {     console.log('nextTick in setTimeout');   }) }, 0)  setTimeout(() => {   console.log('setTimeout 1');   Promise.resolve().then(() => {     console.log('promise in setTimeout1');   })   process.nextTick(() => {     console.log('nextTick in setTimeout1');   }) }, 1)  setImmediate(() => {   console.log('setImmediate');   process.nextTick(() => {     console.log('nextTick in setImmediate');   }) });  process.nextTick(() => {   console.log('nextTick 1');   process.nextTick(() => {     console.log('nextTick 2');   }) })  console.log('global ------');  Promise.resolve().then(() => {   console.log('promise 1');   process.nextTick(() => {     console.log('nextTick in promise');   }) })  /** 執(zhí)行順序如下 global ------ nextTick 1 nextTick 2 promise 1 nextTick in promise setTimeout 0 // 解釋問題1. 沒有上邊的nextTick和promise,setTimeout和setImmediate的順序不一定,有了以后肯定是0先開始。 // 可見,執(zhí)行一個隊列之前,就先檢查并執(zhí)行了nextTick和promise微隊列 nextTick in setTimeout setTimeout 1 nextTick in setTimeout1 promise in setTimeout1 setImmediate nextTick in setImmediate poll 1 nextTick in poll ====== setTimeout 100 nextTick in setTimeout100 promise in setTimeout100 setImmediate in setTimeout 100 nextTick in setImmediate in setTimeout 100 setTimeout 100 - 0 nextTick in setTimeout 100 - 0  */

          以上代碼執(zhí)行多次,順序不變,setTimeout和setImmediate的順序都沒變。

          執(zhí)行順序及具體原因說明如下:

          • global :主線程同步任務(wù),率先執(zhí)行沒毛病

          • nextTick 1:執(zhí)行異步宏任務(wù)之前,清空異步微任務(wù),nextTick優(yōu)先級高,先行一步

          • nextTick 2:執(zhí)行完上邊這句代碼,又一個nextTick微任務(wù),立即率先執(zhí)行

          • promise 1:執(zhí)行異步宏任務(wù)之前,清空異步微任務(wù),Promise的優(yōu)先級低,所以在nextTick完了以后立即執(zhí)行

          • nextTick in promise:清空Promise隊列的過程中,遇到nextTick微任務(wù),立即執(zhí)行、清空

          • setTimeout 0: 解釋第一個問題. 沒有上邊的nextTick和promise,只有setTimeout和setImmediate時他倆的執(zhí)行順序不一定。有了以后肯定是0先開始。可見,執(zhí)行一個宏隊列之前,就先按順序檢查并執(zhí)行了nextTick和promise微隊列。等微隊列全部執(zhí)行完畢,setTimeout(0)的時機也成熟了,就被執(zhí)行。

          • nextTick in setTimeout:執(zhí)行完上邊這句代碼,又一個nextTick微任務(wù),立即率先執(zhí)行 【這種回調(diào)函數(shù)里的微任務(wù),我不能確定是緊隨同步任務(wù)執(zhí)行的;還是放到微任務(wù)隊列,等下一個宏任務(wù)執(zhí)行前再清空的他們。但是順序看上去和立即執(zhí)行他們一樣。不過我比較傾向于是后者:先放到微任務(wù)隊列等待,下一個宏任務(wù)執(zhí)行前清空他們?!?/em>

          • setTimeout 1:因為執(zhí)行微任務(wù)耗費時間,導(dǎo)致此時timers里判斷兩個0和1的setTimeout計時器已經(jīng)結(jié)束,所以兩個setTimeout回調(diào)都已加入隊列并被執(zhí)行

          • nextTick in setTimeout1:執(zhí)行完上邊這句代碼,又一個nextTick微任務(wù),立即率先執(zhí)行 【可能是下一個宏任務(wù)前清空微任務(wù)】

          • promise in setTimeout1:執(zhí)行完上邊這句代碼,又一個Promise微任務(wù),立即緊隨執(zhí)行 【可能是下一個宏任務(wù)前清空微任務(wù)】

          • setImmediate:poll隊列回調(diào)時機未到,先行向下到check隊列,清空隊列,立即執(zhí)行setImmediate回調(diào)

          • nextTick in setImmediate:執(zhí)行完上邊這句代碼,又一個nextTick微任務(wù),立即率先執(zhí)行 【可能是下一個宏任務(wù)前清空微任務(wù)】

          • poll 1:poll隊列實際成熟,回調(diào)觸發(fā),同步任務(wù)執(zhí)行。

          • nextTick in poll :執(zhí)行完上邊這句代碼,又一個nextTick微任務(wù),立即率先執(zhí)行 【可能是下一個宏任務(wù)前清空微任務(wù)】

          • setTimeout 100:定時器任務(wù)到達時間,執(zhí)行回調(diào)。并在回調(diào)里往微任務(wù)推入了nextTick、Promise,往宏任務(wù)的check里推入了setImmediate的回調(diào)。并且也開啟了計時器線程,往timers里增加了下一輪回調(diào)的可能。

          • nextTick in setTimeout100:宏任務(wù)向下前,率先執(zhí)行定時器回調(diào)內(nèi)新增的微任務(wù)-nextTick 【這里就能確定了,是下一個宏任務(wù)前清空微任務(wù)的流程】

          • promise in setTimeout100:緊接著執(zhí)行定時器回調(diào)內(nèi)新增的微任務(wù)-Promise 【清空完nextTick清空Promise的順序】

          • setImmediate in setTimeout 100:這次setImmediate比setTimeout(0)先執(zhí)行的原因是:流程從timers向后走到check隊列,已經(jīng)有了setImmediate的回調(diào),立即執(zhí)行。

          • nextTick in setImmediate in setTimeout 100:執(zhí)行完上邊這句代碼,又一個nextTick微任務(wù),下一個宏任務(wù)前率先清空微任務(wù)

          • setTimeout 100 - 0:輪詢又一次回到timers,執(zhí)行100-0的回調(diào)。

          • nextTick in setTimeout 100 - 0:執(zhí)行完上邊這句代碼,又一個nextTick微任務(wù),下一個宏任務(wù)前率先清空微任務(wù)。

          擴展:為什么有了setImmediate還要有nextTick和Promise?

          一開始設(shè)計的時候,setImmediate充當了微隊列的作用(雖然他不是)。設(shè)計者希望執(zhí)行完poll后立即執(zhí)行setImmediate(當然現(xiàn)在也確實是這么表現(xiàn)的)。所以起的名字叫Immediate,表示立即的意思。 但是后來問題是,poll里可能有N個任務(wù)連續(xù)執(zhí)行,在執(zhí)行期間想要執(zhí)行setImmediate是不可能的。因為poll隊列不停,流程不向下執(zhí)行。

          于是出現(xiàn)nextTick,真正的微隊列概念。但此時,immediate的名字被占用了,所以名字叫nextTick(下一瞬間)。事件循環(huán)期間,執(zhí)行任何一個隊列之前,都要檢查他是否被清空。其次是Promise。

          面試題

          最后,檢驗學習成果的面試題來了

          async function async1() {   console.log('async start');   await async2();   console.log('async end'); }  async function async2(){   console.log('async2'); } console.log('script start');  setTimeout(() => {   console.log('setTimeout 0'); }, 0)  setTimeout(() => {   console.log('setTimeout 3'); }, 3)  setImmediate(() => {   console.log('setImmediate'); })  process.nextTick(() => {   console.log('nextTick'); })  async1();  new Promise((res) => {   console.log('promise1');   res();   console.log('promise2'); }).then(() => {   console.log('promise 3'); });  console.log('script end');  // 答案如下 // - // - // - // - // - // - // - // - // - // - // - // -       /** script start async start async2 promise1 promise2 script end  nextTick async end promise 3  // 后邊這仨的運行順序就是驗證你電腦運算速度的時候了。 速度最好(執(zhí)行上邊的同步代碼 + 微任務(wù) + 計時器運算用了不到0ms): setImmediate setTimeout 0 setTimeout 3  速度中等(執(zhí)行上邊的同步代碼 + 微任務(wù) + 計時器運算用了0~3ms以上): setTimeout 0 setImmediate setTimeout 3  速度較差(執(zhí)行上邊的同步代碼 + 微任務(wù) + 計時器運算用了3ms以上): setTimeout 0 setTimeout 3 setImmediate */

          思維腦圖 – Node生命周期核心階段

          圖文結(jié)合帶你搞懂Nodejs中的事件循環(huán)

          圖文結(jié)合帶你搞懂Nodejs中的事件循環(huán)

          贊(0)
          分享到: 更多 (0)
          網(wǎng)站地圖   滬ICP備18035694號-2    滬公網(wǎng)安備31011702889846號