node端事件循环机制(Part2 – Timers、 Immediates 、 Next Ticks)

欢迎回来,在第一篇文章中, 描述了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…. (自备梯子)

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理