欢迎回来,在第一篇文章中, 描述了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
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。