JavaScript Event loop 事件循环

Event Loop

本文以 Node.js 为例,讲解 Event Loop 在 Node.js 的实现,原文,JavaScript 中的实现大同小异。javascript

什么是 Event Loop ?

单线程的 Node.js 可以实现无阻塞IO的缘由就是事件循环(Event Loop)。java

如今大多数系统内核是多线程的,因此它们能够在后台执行多个操做,当这些操做完成时,内核就会通知 Node.js,而这些操做的回调函数被添加到事件轮询列表(poll queue),而且 Node.js 会在适当的时机执行回调函数。node

概览 Event Loop

当 Node.js 开始执行时,便初始化 Event Loop,执行过程当中会存在许多异步操做,如:REPL、定时器(timers)、调用异步 API(请求,事件监听),在主进程代码执行完后,便开始运行 Event Loopgit

下图描述了 Event Loop 中的各个阶段github

┌───────────────────────┐
┌─>│        timers         │ 这个阶段执行 `setTimeout()` 和 `setInterval()` 中的回调函数
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │     I/O callbacks     │ 这个阶段执行除了 `close` 回调函数之外的几乎全部的 I/0 回调函数
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │     idle, prepare     │ 这个阶段仅仅 Node.js 内部使用
│  └──────────┬────────────┘      ┌───────────────┐
│  ┌──────────┴────────────┐      │   incoming:   │
│  │         poll          │<─────┤  connections, │ 执行队列中的回调函数、检索新的回调函数
│  └──────────┬────────────┘      │   data, etc.  │
│  ┌──────────┴────────────┐      └───────────────┘
│  │        check          │ `setImmediate()` 将在这里被调用
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
└──┤    close callbacks    │ `close` 回调函数被调用如:socket.on('close', ...)
   └───────────────────────┘

详解 Event Loop 的各个阶段

timers

setTimeout() 和 setInterval() 都要指定一个运行时间,这个运行时间其实不是确切的运行时间,而是一个指望时间,Event Loop 会在 timers 阶段执行超过时望时间的定时器回调函数,但因为你不肯定在其余阶段甚至主进程中的事件执行时间,因此定时器不必定会按时执行。多线程

var asyncApi = function (callback) {
  setTimeout(callback, 90)
}

const timeoutScheduled = Date.now();
setTimeout(() => {
  const delay = Date.now() - timeoutScheduled;
  console.log(`${delay}ms setTimeout 被执行`); // 140ms 以后被执行
}, 100);

asyncApi(() => {
  const startCallback = Date.now();
  while (Date.now() - startCallback < 50) {
    // do nothing
  }
})
I/O callbacks

这个阶段主要执行一些系统操做带来的回调函数,如 TCP 错误,若是 TCP 尝试连接时出现 ECONNREFUSED 错误 ,一些 *nix 会把这个错误报告给 Node.js。而这个错误报告会先进入队列中,而后在 I/O callbacks 阶段执行。异步

poll

poll 阶段有两个主要功能:socket

  1. 也会执行时间定时器到达指望时间的回调函数async

  2. 执行事件循环列表(poll queue)里的函数ide

当 Event Loop 进入 poll 阶段而且没有其他的定时器,那么:

  • 若是事件循环列表不为空,则迭代同步的执行队列中的函数。

  • 若是事件循环列表为空,则判断是否有 setImmediate() 函数待执行。若是有结束 poll 阶段,直接到

check 阶段。若是没有,则等待回调函数进入队列并当即执行。

check

在 poll 阶段结束以后,执行 setImmediate()

close

忽然结束的事件的回调函数会在这里触发,若是 socket.destroy(),那么 close 会被触发在这个阶段,也有可能经过 process.nextTick() 来触发。

setImmediate()、setTimeout()、process.nextTick()

这里要说明一下 process.nextTick() 是在下次事件循环以前运行,若是把 process.nextTick()setImmediate() 写在一块儿,那么是 process.nextTick() 先执行。nextimmediate 快,官方也说这个函数命名有问题,可是由于历史存留没办法解决。

process.nextTick(() => {
  console.log('nextTick');
});
setImmediate(() => {
  console.log('setImmediate');
});
setTimeout(() => {
  console.log('setTimeout'); 
}, 0)

// 执行结果,nextTick, setTimeout, setImmediate
// 查看 Node.js 源码,setTimeout(fun, 0) 会转化成 setTimeout(fun, 1),因此在这种简单的状况下,对于不一样设备,setImmediate 有可能早于 setTimeout 执行。

总结

理解事件循环,会知道 JavaScript 如何无阻塞运行的,以及它简洁的开发思路和事件驱动风格。


做者:肖沐宸,github

相关文章
相关标签/搜索