我爱编程

理解JS之事件处理机制Event loop

2018-04-01  本文已影响0人  花生酱031

原文传送门
https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/

单线程的javascript能够实现无阻塞IO的原因就是事件循环(Event Loop)。

In computer science, the event loop, message dispatcher, message loop, message pump, or run loop is a programming construct that waits for and dispatches events or messages in a program. -- from wiki

大概含义: EventLoop 是一种常用的机制,通过对内部或外部的事件提供者发出请求, 如文件读写, 网络连接等异步操作, 完成后调用事件处理程序. 整个过程都是异步阶段

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

   ┌───────────────────────┐
┌─>│        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', ...)
   └───────────────────────┘

对各个阶段的说明:

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阶段有两个主要功能:

也会执行时间定时器到达期望时间的回调函数

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

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() 先执行。next 比 immediate 快,官方也说这个函数命名有问题,但是因为历史存留没办法解决。

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 执行。
上一篇 下一篇

猜你喜欢

热点阅读