| 本文涵蓋
 面試題面試題如下,大家可以先試著寫一下輸出結(jié)果,然后再看我下面的詳細(xì)講解,看看會不會有什么出入,如果把整個順序弄清楚 Node.js 的執(zhí)行順序應(yīng)該就沒問題了。 面試題正確的輸出結(jié)果 提出問題在理解node.js的異步的時候有一些不懂的地方,使用node.js的開發(fā)者一定都知道它是單線程的,異步不阻塞且高并發(fā)的一門語言,但是node.js在實現(xiàn)異步的時候,兩個異步任務(wù)開啟了,是就是誰快就誰先完成這么簡單,還是說異步任務(wù)最后也會有一個先后執(zhí)行順序?對于一個單線程的的異步語言它是怎么實現(xiàn)高并發(fā)的呢? 好接下來我們就帶著這兩個問題來真正的理解node.js中的異步(微任務(wù)與事件循環(huán))。 Node 的異步語法比瀏覽器更復(fù)雜,因為它可以跟內(nèi)核對話,不得不搞了一個專門的庫 libuv 做這件事。這個庫負(fù)責(zé)各種回調(diào)函數(shù)的執(zhí)行時間,異步任務(wù)最后基于事件循環(huán)機制還是要回到主線程,一個個排隊執(zhí)行。 我自己是一名從事了多年開發(fā)的web前端老程序員,目前辭職在做自己的web前端私人定制課程,今年年初我花了一個月整理了一份最適合2019年學(xué)習(xí)的web前端學(xué)習(xí)干貨,各種框架都有整理,送給每一位前端小伙伴,想要獲取的可以關(guān)注我并添加我的web前端交流裙:600610151,即可免費獲取。 詳細(xì)講解1.本輪循環(huán)與次輪循環(huán)異步任務(wù)可以分成兩種。 
 所謂”循環(huán)”,指的是事件循環(huán)(event loop)。這是 JavaScript 引擎處理異步任務(wù)的方式,后文會詳細(xì)解釋。這里只要理解,本輪循環(huán)一定早于次輪循環(huán)執(zhí)行即可。 Node 規(guī)定,process.nextTick和Promise的回調(diào)函數(shù),追加在本輪循環(huán),即同步任務(wù)一旦執(zhí)行完成,就開始執(zhí)行它們。而setTimeout、setInterval、setImmediate的回調(diào)函數(shù),追加在次輪循環(huán)。 2.process.nextTick()1)process.nextTick不要因為有next就被好多小伙伴當(dāng)作次輪循環(huán)。 2)Node 執(zhí)行完所有同步任務(wù),接下來就會執(zhí)行 process.nextTick 的任務(wù)隊列。 3)開發(fā)過程中如果想讓異步任務(wù)盡可能快地執(zhí)行,可以使用 process.nextTick 來完成。 3.微任務(wù)(microtack)根據(jù)語言規(guī)格,Promise 對象的回調(diào)函數(shù),會進(jìn)入異步任務(wù)里面的”微任務(wù)”(microtask)隊列。 微任務(wù)隊列追加在 process.nextTick 隊列的后面,也屬于本輪循環(huán)。 根據(jù)語言規(guī)格,Promise 對象的回調(diào)函數(shù),會進(jìn)入異步任務(wù)里面的”微任務(wù)”(microtask)隊列。 微任務(wù)隊列追加在process.nextTick隊列的后面,也屬于本輪循環(huán)。所以,下面的代碼總是先輸出3,再輸出4。 // 輸出結(jié)果3,4 // 輸出結(jié)果 1,3,2,4 注意,只有前一個隊列全部清空以后,才會執(zhí)行下一個隊列。兩個隊列的概念 nextTickQueue 和微隊列 microTaskQueue,也就是說開啟異步任務(wù)也分為幾種,像 Promise 對象這種,開啟之后直接進(jìn)入微隊列中,微隊列內(nèi)的就是那個任務(wù)快就那個先執(zhí)行完,但是針對于隊列與隊列之間不同的任務(wù),還是會有先后順序,這個先后順序是由隊列決定的。 4.事件循環(huán)的階段(idle, prepare忽略了這個階段)事件循環(huán)最階段最詳細(xì)的講解(官網(wǎng):https:///en/docs/guides/event-loop-timers-and-nexttick/#setimmediate-vs-settimeout) 
 
 1)Node 開始執(zhí)行腳本時,會先進(jìn)行事件循環(huán)的初始化,但是這時事件循環(huán)還沒有開始,會先 完成下面的事情。 同步任務(wù) 發(fā)出異步請求 規(guī)劃定時器生效的時間 執(zhí)行process.nextTick()等等 最后,上面這些事情都干完了,事件循環(huán)就正式開始了。 2)事件循環(huán)同樣運行在單線程環(huán)境下,高并發(fā)也是依靠事件循環(huán),每產(chǎn)生一個事件,就會加入到該階段對應(yīng)的隊列中,此時事件循環(huán)將該隊列中的事件取出,準(zhǔn)備執(zhí)行之后的 Callback。 3)假設(shè)事件循環(huán)現(xiàn)在進(jìn)入了某個階段,即使這期間有其他隊列中的事件就緒,也會先將當(dāng)前隊列的全部回調(diào)方法執(zhí)行完畢后,再進(jìn)入到下一個階段。 5.事件循環(huán)中的setTimeOut與setImmediate由于 setTimeout 在 timers 階段執(zhí)行,而 setImmediate 在 check 階段執(zhí)行。所以,setTimeout 會早于 setImmediate 完成。 上面代碼應(yīng)該先輸出1,再輸出2,但是實際執(zhí)行的時候,結(jié)果卻是不確定,有時還會先輸出2,再輸出1。 這是因為 setTimeout 的第二個參數(shù)默認(rèn)為0。但是實際上,Node 做不到0毫秒,最少也需要1毫秒,根據(jù)官方文檔,第二個參數(shù)的取值范圍在1毫秒到2147483647毫秒之間。也就是說,setTimeout(f, 0)等同于setTimeout(f, 1)。 實際執(zhí)行的時候,進(jìn)入事件循環(huán)以后,有可能到了1毫秒,也可能還沒到1毫秒,取決于系統(tǒng)當(dāng)時的狀況。如果沒到1毫秒,那么 timers 階段就會跳過,進(jìn)入 check 階段,先執(zhí)行 setImmediate 的回調(diào)函數(shù)。 但是,下面的代碼一定是先輸出2,再輸出1。 上面代碼會先進(jìn)入 I/O callbacks 階段,然后是 check 階段,最后才是 timers 階段。因此,setImmediate才會早于setTimeout執(zhí)行。 6.同步任務(wù)中async以及promise的一些誤解
 在面試題中,在同步任務(wù)的過程中,不知道大家有沒有疑問,為什么不是執(zhí)行完async2輸出后執(zhí)行async1 end輸出,而是接著執(zhí)行 promise1? 
 簡單的說,先去執(zhí)行后面的同步任務(wù)代碼,執(zhí)行完成后,也就是表達(dá)式中的 Promise 解析完成后繼續(xù)執(zhí)行 async 函數(shù)并返回解決結(jié)果。(其實還是本輪循環(huán)promise的問題,最后的resolve屬于異步,位于本輪循環(huán)的末尾。) 
 console.log('promise2')為什么也是在resolve之前執(zhí)行? 解答:注:此內(nèi)容來源與阮一峰老師的ES6書籍,調(diào)用resolve或者reject并不會終結(jié)promise的參數(shù)函數(shù)的執(zhí)行。因為立即resolved的Promise是本輪循環(huán)的末尾執(zhí)行,同時總是晚于本輪循環(huán)的同步任務(wù)。正規(guī)的寫法調(diào)用resolve或者reject以后,Promise的使命就完成了,后繼操作應(yīng)該放在then方法后面。所以最好在它的前面加上return語句,這樣就不會出現(xiàn)意外 
 promise3和script end的執(zhí)行順序是否有疑問? 解答:因為立即resolved的Promise是本輪循環(huán)的末尾執(zhí)行,同時總是晚于本輪循環(huán)的同步任務(wù)。Promise 是一個立即執(zhí)行函數(shù),但是他的成功(或失敗:reject)的回調(diào)函數(shù) resolve 卻是一個異步執(zhí)行的回調(diào)。當(dāng)執(zhí)行到 resolve() 時,這個任務(wù)會被放入到回調(diào)隊列中,等待調(diào)用棧有空閑時事件循環(huán)再來取走它。本輪循環(huán)中最后執(zhí)行的。 整體結(jié)論
 順序的整體總結(jié)就是: 同步任務(wù)-> 本輪循環(huán)->次輪循環(huán) | 
|  |