2

欢迎回来,在第一篇文章中, 描述了NodeJS事件循环的总体情况,在这一节中,将通过代码实例详细讨论三种重要的队列。它们是,timers, immediatesprocess.nextTick的回掉.

图片描述

文章指引

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')
    })
});

接下来我们来看一下它的执行流程

  1. 在开始时,这个程序使用fs.readFile异步读取当前文件。它提供一个回调函数,在读取文件后触发。
  2. 然后事件循环开始。
  3. 一旦读取文件后,它将在事件循环的I/O队列中添加事件(要执行的回调)。
  4. 由于没有要处理的其他事件,Node正在等待任何I/O事件。然后,它将在I/O队列中看到文件读取事件并执行它。
  5. 在回调的执行过程中,timer 被添加到timer 堆中,并且Immediates被添加到Immediates 队列中
  6. 现在我们知道事件循环处于I/O阶段。由于没有要处理的任何I/O事件,因此事件循环将移动到immediate阶段,然后立即执行immediate 回调。
  7. 在事件循环的下一个循环中,它将看到过期的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'));
  1. 首先会有如下的一些事件被添加到事件队列中

    • 3 immediates
    • 5 timer 回掉
    • 5 next tick 回掉
  2. 当事件循环开始时,它将注意到next tick队列并开始处理next tick的回调。在执行第二个next tick回调期间,一个新的next tick回调被添加到next tick队列的末尾,并将在next tick队列的末尾执行。
  3. 将执行过期timer 的回调。在第二个timer 回调函数的执行过程中,一个事件被添加到next tick队列中。
  4. 一旦执行了所有过期timer 的回调,事件循环将看到下一个timer 队列中有一个事件(这是在执行第二个timer 回调期间添加的)。然后事件循环将执行它。
  5. 由于没有要处理的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


RickyLong
501 声望27 粉丝

所有事情都有一套底层的方法论,主要找到关键点,然后刻意练习,没有刻意练习,做事情只是低效率的重复