欢迎回来,在第一篇文章中, 描述了 NodeJS 事件循环的总体情况,在这一节中,将通过代码实例详细讨论三种重要的队列。它们是,timers, immediates 和 process.nextTick 的回掉.
文章指引
Event Loop
Timers、Immediates、Next Ticks (本文)
Promises、Next-Ticks、Immediates
处理 I/O
最佳的事件循环练习
在 Node v11 中 timers、microtasks 发生的改变
I/ O 饥饿 例子一
这带来了一个新问题。使用 process 递归 / 重复地向 nextTick 队列添加事件。nextTick 函数可能导致 I / O 和其他队列永远饿死。我们可以使用下面的简单脚本模拟这个场景。
const fs = require(‘fs’);
function addNextTickRecurs(count) {
let self = this;
if (self.id === undefined) {
self.id = 0;
}
if (self.id === count) return;
process.nextTick(() => {
console.log(`process.nextTick call ${++self.id}`);
addNextTickRecurs.call(self, count);
});
}
addNextTickRecurs(Infinity);
setTimeout(console.log.bind(console, ‘omg! setTimeout was called’), 10);
setImmediate(console.log.bind(console, ‘omg! setImmediate also was called’));
fs.readFile(__filename, () => {
console.log(‘omg! file read complete callback was called!’);
});
console.log(‘started’);
您可以看到输出是一个无限循环的 nextTick 回调调用,以及 setTimeout、setImmediate 和 fs.readFile。从未调用 readFile 回调.
started
process.nextTick call 1
process.nextTick call 2
process.nextTick call 3
process.nextTick call 4
process.nextTick call 5
process.nextTick call 6
process.nextTick call 7
process.nextTick call 8
process.nextTick call 9
process.nextTick call 10
process.nextTick call 11
process.nextTick call 12
….
例二
const fs = require(‘fs’);
fs.readFile(__filename, () => {
setTimeout(() => {
console.log(‘timeout’)
}, 0);
setImmediate(() => {
console.log(‘immediate’)
})
});
接下来我们来看一下它的执行流程
在开始时,这个程序使用 fs.readFile 异步读取当前文件。它提供一个回调函数,在读取文件后触发。
然后事件循环开始。
一旦读取文件后,它将在事件循环的 I / O 队列中添加事件 (要执行的回调)。
由于没有要处理的其他事件,Node 正在等待任何 I / O 事件。然后,它将在 I / O 队列中看到文件读取事件并执行它。
在回调的执行过程中,timer 被添加到 timer 堆中,并且 Immediates 被添加到 Immediates 队列中
现在我们知道事件循环处于 I / O 阶段。由于没有要处理的任何 I / O 事件,因此事件循环将移动到 immediate 阶段,然后立即执行 immediate 回调。
在事件循环的下一个循环中,它将看到过期的 timer 并执行 timer 回调。
例三
setImmediate(() => console.log(‘this is set immediate 1’));
setImmediate(() => console.log(‘this is set immediate 2’));
setImmediate(() => console.log(‘this is set immediate 3’));
setTimeout(() => console.log(‘this is set timeout 1’), 0);
setTimeout(() => {
console.log(‘this is set timeout 2’);
process.nextTick(() => console.log(‘this is process.nextTick added inside setTimeout’));
}, 0);
setTimeout(() => console.log(‘this is set timeout 3’), 0);
setTimeout(() => console.log(‘this is set timeout 4’), 0);
setTimeout(() => console.log(‘this is set timeout 5’), 0);
process.nextTick(() => console.log(‘this is process.nextTick 1’));
process.nextTick(() => {
process.nextTick(console.log.bind(console, ‘this is the inner next tick inside next tick’));
});
process.nextTick(() => console.log(‘this is process.nextTick 2’));
process.nextTick(() => console.log(‘this is process.nextTick 3’));
process.nextTick(() => console.log(‘this is process.nextTick 4’));
首先会有如下的一些事件被添加到事件队列中
3 immediates
5 timer 回掉
5 next tick 回掉
当事件循环开始时,它将注意到 next tick 队列并开始处理 next tick 的回调。在执行第二个 next tick 回调期间,一个新的 next tick 回调被添加到 next tick 队列的末尾,并将在 next tick 队列的末尾执行。
将执行过期 timer 的回调。在第二个 timer 回调函数的执行过程中,一个事件被添加到 next tick 队列中。
一旦执行了所有过期 timer 的回调,事件循环将看到下一个 timer 队列中有一个事件 (这是在执行第二个 timer 回调期间添加的)。然后事件循环将执行它。
由于没有要处理的 I / O 事件,因此事件循环将移动到即时队列并处理即时队列。
执行结果是下面这个样子
this is process.nextTick 1
this is process.nextTick 2
this is process.nextTick 3
this is process.nextTick 4
this is the inner next tick inside next tick
this is set timeout 1
this is set timeout 2
this is set timeout 3
this is set timeout 4
this is set timeout 5
this is process.nextTick added inside setTimeout
this is set immediate 1
this is set immediate 2
this is set immediate 3
Reference
https://jsblog.insiderattack…. (自备梯子)