近日在論壇上看到一篇文章講node和谷歌瀏覽器的eventloop的區別,因為看到寫的還不錯,我表示了肯定。但沒過多久一位壇友卻說node11結果不一樣,我說怎麼可能不一樣。接著壇友貼了個程式碼,我試著執行了一下,啪啪打臉!
一探究竟
先上被啪啪打臉的程式碼:
setTimeout(() => {
console.log('timer1');
Promise.resolve().then(function() {
console.log('promise1');
});
}, 0);
setTimeout(() => {
console.log('timer2');
Promise.resolve().then(function() {
console.log('promise2');
});
}, 0);
複製程式碼
瞭解node的eventloop的同學應該會這樣想:
- 理想情況下這個就是一開始將兩個setTimeout放進timers的階段。
- 等到時間到達後執行timer1,把promise1的Promise放入timers的下一階段微任務佇列中,同理繼續執行timers的階段,執行timer2,把promise2的Promise放入timers的下一階段微任務佇列中。
- 直到timers佇列全部執行完,才開始執行微任務佇列,也就是promise1和promise2.
那麼如果機器執行良好就是以下結果:
timer1
timer2
promise1
promise2
複製程式碼
node10執行結果確實是這樣,是沒問題的。但node11執行後居然是:
timer1
promise1
timer2
promise2
複製程式碼
挺吃驚的,但吃驚過後還是仔細去翻node的修改日誌,在node 11.0 的修改日誌裡面發現了這個:
- Timers
- Interval timers will be rescheduled even if previous interval threw an error. #20002
- nextTick queue will be run after each immediate and timer. #22842
然後分別看了20002和22842的PR,發現在 #22842 在lib/timers.js裡面有以下增加:
這兩個是什麼意思呢?
提示一下runNextTicks()就是process._tickCallback()。用過的可能知道這個就是除了處理一些非同步鉤子,然後就是執行微任務佇列的。於是我增加了兩行process._tickCallback()在setTimeout方法尾部,再使用node10執行,效果果然和node11一致,程式碼如下:
setTimeout(() => {
console.log('timer1');
Promise.resolve().then(function() {
console.log('promise1');
});
process._tickCallback(); // 這行是增加的!
}, 0);
setTimeout(() => {
console.log('timer2');
Promise.resolve().then(function() {
console.log('promise2');
});
process._tickCallback(); // 這行是增加的!
}, 0);
複製程式碼
那麼為什麼要這麼做呢?
當然是為了和瀏覽器更加趨同。
瞭解瀏覽器的eventloop可能就知道,瀏覽器的巨集任務佇列執行了一個,就會執行微任務。
簡單的說,可以把瀏覽器的巨集任務和node10的timers比較,就是node10只有全部執行了timers階段佇列的全部任務才執行微任務佇列,而瀏覽器只要執行了一個巨集任務就會執行微任務佇列。
現在node11在timer階段的setTimeout,setInterval...和在check階段的immediate都在node11裡面都修改為一旦執行一個階段裡的一個任務就立刻執行微任務佇列。
最後
所以在生產環境建議還是不要特意的去利用node和瀏覽器不同的一些特性。即使是node和瀏覽器相同的特性,但規範沒確定的一些特性,也建議小心使用。否則一次小小的node升級可能就會造成一次線上事故,而不只是啪啪打臉這麼簡單了。