面試題以下,你們能夠先試着寫一下輸出結果,而後再看我下面的詳細講解,看看會不會有什麼出入,若是把整個順序弄清楚node.js的執行順序應該就沒問題了。javascript
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('setTimeout0')
},0)
setTimeout(function(){
console.log('setTimeout3')
},3)
setImmediate(() => console.log('setImmediate'));
process.nextTick(() => console.log('nextTick'));
async1();
new Promise(function(resolve){
console.log('promise1')
resolve();
console.log('promise2')
}).then(function(){
console.log('promise3')
})
console.log('script end')
複製代碼
面試題正確的輸出結果html
script start
async1 start
async2
promise1
promise2
script end
nextTick
async1 end
promise3
setTimeout0
setImmediate
setTimeout3
複製代碼
在理解node.js的異步的時候有一些不懂的地方,使用node.js的開發者必定都知道它是單線程的,異步不阻塞且高併發的一門語言,可是node.js在實現異步的時候,兩個異步任務開啓了,是就是誰快就誰先完成這麼簡單,仍是說異步任務最後也會有一個前後執行順序?對於一個單線程的的異步語言它是怎麼實現高併發的呢?java
好接下來咱們就帶着這兩個問題來真正的理解node.js中的異步(微任務與事件循環)。node
Node 的異步語法比瀏覽器更復雜,由於它能夠跟內核對話,不得不搞了一個專門的庫 libuv 作這件事。這個庫負責各類回調函數的執行時間,異步任務最後基於事件循環機制仍是要回到主線程,一個個排隊執行。程序員
異步任務能夠分紅兩種。面試
所謂」循環」,指的是事件循環(event loop)。這是 JavaScript 引擎處理異步任務的方式,後文會詳細解釋。這裏只要理解,本輪循環必定早於次輪循環執行便可。api
Node 規定,process.nextTick和Promise的回調函數,追加在本輪循環,即同步任務一旦執行完成,就開始執行它們。而setTimeout、setInterval、setImmediate的回調函數,追加在次輪循環。promise
1)process.nextTick不要由於有next就被好多小夥伴看成次輪循環。瀏覽器
2)Node 執行完全部同步任務,接下來就會執行process.nextTick的任務隊列。微信
3)開發過程當中若是想讓異步任務儘量快地執行,可使用process.nextTick來完成。
根據語言規格,Promise對象的回調函數,會進入異步任務裏面的」微任務」(microtask)隊列。
微任務隊列追加在process.nextTick隊列的後面,也屬於本輪循環。
根據語言規格,Promise對象的回調函數,會進入異步任務裏面的」微任務」(microtask)隊列。
微任務隊列追加在process.nextTick隊列的後面,也屬於本輪循環。因此,下面的代碼老是先輸出3,再輸出4。
process.nextTick(() => console.log(3));
Promise.resolve().then(() => console.log(4));
複製代碼
//輸出結果3,4
process.nextTick(() => console.log(1));
Promise.resolve().then(() => console.log(2));
process.nextTick(() => console.log(3));
Promise.resolve().then(() => console.log(4));
複製代碼
//輸出結果 1,3,3,4
注意,只有前一個隊列所有清空之後,纔會執行下一個隊列。兩個隊列的概念 nextTickQueue 和微隊列microTaskQueue,也就是說開啓異步任務也分爲幾種,像promise對象這種,開啓以後直接進入微隊列中,微隊列內的就是那個任務快就那個先執行完,可是針對於隊列與隊列之間不一樣的任務,仍是會有前後順序,這個前後順序是由隊列決定的。
事件循環最階段最詳細的講解(官網:nodejs.org/en/docs/gui…
timers階段
次階段包括setTimeout()和setInterval()
IO callbacks
大部分的回調事件,普通的caollback
poll階段
網絡鏈接,數據獲取,讀取文件等操做
check階段
setImmediate()在這裏調用回調
close階段 一些關閉回調,例如socket.on('close', ...)
1)Node 開始執行腳本時,會先進行事件循環的初始化,可是這時事件循環尚未開始,會先 完成下面的事情。
同步任務 發出異步請求 規劃定時器生效的時間 執行process.nextTick()等等
最後,上面這些事情都幹完了,事件循環就正式開始了。
2)事件循環一樣運行在單線程環境下,高併發也是依靠事件循環,每產生一個事件,就會加入到該階段對應的隊列中,此時事件循環將該隊列中的事件取出,準備執行以後的callback。
3)假設事件循環如今進入了某個階段,即便這期間有其餘隊列中的事件就緒,也會先將當前隊列的所有回調方法執行完畢後,再進入到下一個階段。
因爲setTimeout在 timers 階段執行,而setImmediate在 check 階段執行。因此,setTimeout會早於setImmediate完成。
setTimeout(() => console.log(1));
setImmediate(() => console.log(2));
複製代碼
上面代碼應該先輸出1,再輸出2,可是實際執行的時候,結果倒是不肯定,有時還會先輸出2,再輸出1。
這是由於setTimeout的第二個參數默認爲0。可是實際上,Node 作不到0毫秒,最少也須要1毫秒,根據官方文檔,第二個參數的取值範圍在1毫秒到2147483647毫秒之間。也就是說,setTimeout(f, 0)等同於setTimeout(f, 1)。
實際執行的時候,進入事件循環之後,有可能到了1毫秒,也可能還沒到1毫秒,取決於系統當時的情況。若是沒到1毫秒,那麼 timers 階段就會跳過,進入 check 階段,先執行setImmediate的回調函數。
可是,下面的代碼必定是先輸出2,再輸出1。
const fs = require('fs');
fs.readFile('test.js', () => {
setTimeout(() => console.log(1));
setImmediate(() => console.log(2));
});
複製代碼
上面代碼會先進入 I/O callbacks 階段,而後是 check 階段,最後纔是 timers 階段。所以,setImmediate纔會早於setTimeout執行。
在那道面試題中,在同步任務的過程當中,不知道你們有沒有疑問,爲何不是執行完async2輸出後執行async1 end輸出,而是接着執行promise1?
解答:引用阮一峯老師書中一句話:「 async 函數返回一個 Promise 對象,當函數執行的時候,一旦遇到 await 就會先返回,等到觸發的異步操做完成,再接着執行函數體內後面的語句。」 簡單的說,先去執行後面的同步任務代碼,執行完成後,也就是表達式中的 Promise 解析完成後繼續執行 async 函數並返回解決結果。(其實仍是本輪循環promise的問題,最後的resolve屬於異步,位於本輪循環的末尾。)
console.log('promise2')爲何也是在resolve以前執行?
解答:注:此內容來源與阮一峯老師的ES6書籍,調用resolve或者reject並不會終結promise的參數函數的執行。由於當即resolved的Promise是本輪循環的末尾執行,同時老是晚於本輪循環的同步任務。正規的寫法調用resolve或者reject之後,Promise的使命就完成了,後繼操做應該放在then方法後面。因此最好在它的前面加上return語句,這樣就不會出現意外
new Promise((resolve,reject) => {
return resolve(1);
//後面的語句不會執行
console.log(2);
}
複製代碼
promise3和script end的執行順序是否有疑問?
解答:由於當即resolved的Promise是本輪循環的末尾執行,同時老是晚於本輪循環的同步任務。 Promise 是一個當即執行函數,可是他的成功(或失敗:reject)的回調函數 resolve 倒是一個異步執行的回調。當執行到 resolve() 時,這個任務會被放入到回調隊列中,等待調用棧有空閒時事件循環再來取走它。本輪循環中最後執行的。
順序的總體總結就是: 同步任務-> 本輪循環->次輪循環
node.js官網:
歡迎你們關注個人公衆號——程序員成長指北。請自行微信搜索——「程序員成長指北」