Node.js中的事件循环,Timers和process.nextTick() 的探索之路

事件循环

事件循环就是node.js去作一些非阻塞I/O操做,那么问题来了,非阻塞操做又是什么呢?有一个事实对于js开发者都熟知的是,js是单线程的,也就是说在一段时间内只可以处理一种任务,其余任务要执行须要等待当前任务执行完以后再开始。node

因为大部分的现代内核都是多线程的,它们可以处理不一样的操做执行。当其中的一个操做完成时,内核会告诉node,回调函数将会被加入到执行队列中等待被执行。api

事件循环有好几个阶段,每一个阶段都有先进先出的回调队列被执行。每一个阶段都有它的特别之处,当事件循环进入被给予的阶段时,它将会执行确切的操做,直到上一队列已经执行完接着执行在该阶段的回调,当该阶段上的全部队列或回调都执行完成以后,事件循环会转向下一阶段多线程

阶段预览

  1. times:这个阶段执行把setTimeout()和setInterval()列入计划的回调
  2. pending callbacks:执行I/O回调
  3. idle, prepare: 仅为内部使用
  4. poll: 获取新的I/O事件;执行I/O相关的回调(大部分关闭回调的异常)
  5. check: setImmediate()回调将会被唤起
  6. close callbacks: 一些关闭的回调,例如socket.on('close', ...)

times

一个timer指定了一个阈值,在一个回调被执行以后而不是你想要让它执行的肯定的时间。timer的回调在给定的肯定时间以后将会尽量早的执行,然而,操做系统或者其它回调的执行可能会延迟timer的回调异步

pending callbacks

这个阶段执行一些系统操做的回调,例如tcp错误的类型socket

poll

这个poll阶段有两个主要的函数:tcp

  1. 计算它应该阻塞的时间
  2. 在该队列中处理事件

当事件循环进入poll阶段,而且没有timers被安排执行,如下之一将会发生:ide

  1. 若是poll队列不为空,事件循环将会同步迭代执行它的调用队列直到队列已经被执行完成
  2. 若是poll队列是空的,如下之一将会发生: 若是脚本中有setImmediate,事件循环将会中止poll阶段,继续转向check阶段去执行这些被安排的脚本 若脚本中没有setImmediate,事件循环将会等待被添加进队列的回调,并立刻执行它们

一旦poll队列是空的,事件循环将会检查那些已经到达了设置的阈值的timers。若一个或更多的timers已经准备好,事件循环将会转向执行timers阶段,从而去执行这些timers的回调函数函数

check

这个阶段在poll阶段已经完成以后容许人立刻执行回调,若是poll阶段变为了闲置状态,且脚本中有setImmediate,事件循环将会转向check阶段而不是等待。 setImmediate其实是一个肯定的timer,它运行在一个事件循环的独立的阶段。它使用了一个libuv api,是在poll阶段完成以后,执行这些安排的回调oop

close callbacks

若是一个socket或者是处理忽然的被关闭了,这个close事件将会在这个阶段触发。另外,它将会经过 process.nextTick触发。ui

setImmediate() VS setTimeout()

他们很相似,可是表现却不一样,这取决于他们什么时候被调用

  1. setImmediate()被设计为一旦当前的poll阶段完成时,执行回调
  2. setTimeout()是要在设置一个最小阈值的时间以后执行回调

他们在被调用的不一样环境下执行顺序会有所不一样,然而,若在I/O循环以内,immediate的回调老是先执行

const fs = require('fs');

fs.readFile(__filename, () ={
  setImmediate(() ={
console.log('immediate');
  });
  setTimeout(() ={
console.log('timeout');
  }, 0);
});
复制代码

process.nextTick()

尽管是异步api的一部分,但不是事件循环的部分。这个nextTickQueue将会在当前操做完成以后被处理,无论当前事件循环处在哪一阶段

setImmediate() VS process.nextTick()

  1. 在同一阶段process.nextTick()将会当即触发执行
  2. setImmediate()在下一次迭代循环中触发

为何使用process.nextTick()

  1. 容许用户处理错误,在事件循环继续以前清除不须要的资源或者是再次发出请求
  2. 有时候须要在执行栈结束时但要在事件循环继续以前执行该回调函数

举个栗子,若是要在函数构造器中触发一个事件,若按照如下写法,是不会被调用执行的,由于该代码并无被处理,构造函数尚未被执行完成。

const EventEmitter = require('events');
const util = require('util');

function MyEmitter() {
  EventEmitter.call(this);
  this.emit('event'); // 该事件触发无效
}
util.inherits(MyEmitter, EventEmitter);

const myEmitter = new MyEmitter();
myEmitter.on('event', () => {
  console.log('an event occurred!');
});
复制代码

若使用了process.nextTick(),就能够在构造器执行完成以后触发该事件,从而触发回调

const EventEmitter = require('events');
const util = require('util');

function MyEmitter() {
  EventEmitter.call(this);

  // use nextTick to emit the event once a handler is assigned
  process.nextTick(() => {
this.emit('event');
  });
}
util.inherits(MyEmitter, EventEmitter);

const myEmitter = new MyEmitter();
myEmitter.on('event', () => {
  console.log('an event occurred!');
});
复制代码

参考资料:nodejs.org/en/docs/gui…

相关文章
相关标签/搜索