事件循环是Nodejs能够高效处理并发和异步I/O操作的关键机制。
基本概念
Node.js运行在单个线程上,但是通过事件驱动和非阻塞I/O模型,它可以高效地处理大量并发连接。事件循环是这一机制的核心,它不断地检查是否有待处理的事件(如I/O完成、新的网络连接、定时器触发等),并执行与这些事件相关的回调函数。
主要阶段
Node.js的事件循环分为多个阶段,每个阶段处理特定类型的任务。这些阶段可能根据Node.js的不同版本有所变化,但大致包括:
- Timers:处理
setTimeout()
和setInterval()
设置的回调。 - I/O Polling:检查是否有已完成的I/O操作或准备好的网络连接,并执行相关的回调。
- Check(也称为Immediate):执行
setImmediate()
的回调函数。 - Close Callbacks:处理关闭的事件,如
socket.on('close', ...)
。 - Other Stages:除了上述主要阶段外,还可能有额外阶段,如用于处理Promise的微任务阶段。
微任务与宏任务
在事件循环中,任务被分为两类:
- 宏任务(Macrotasks):包括setTimeout、setInterval、I/O、UI rendering(在浏览器环境中)等。
- 微任务(Microtasks):包括Promise的回调、
process.nextTick
(Node.js特有)、MutationObserver(浏览器环境)等。
每次事件循环迭代结束前,所有当前宏任务执行完成后,会立即执行微任务队列中的所有任务,然后再开始下一轮事件循环。这个过程保证了微任务总是在同一次事件循环迭代中紧接着宏任务之后执行。
执行流程
- 启动:Node.js启动时,先执行全局脚本的同步代码。
- 循环:进入事件循环,按照阶段顺序检查是否有可执行的任务。
- 回调执行:当事件发生或定时器到期,对应的回调被推入相应的队列等待执行。
- 循环结束:理论上事件循环不会自然结束,除非显式退出或因错误崩溃,因为总是有可能有新的异步任务加入到队列中。
注意事项
- 单线程与多线程:虽然JavaScript执行在单线程上,Node.js在内部使用多线程处理某些操作,如文件I/O和网络请求,通过线程池提高效率,但最终的回调仍由单线程的事件循环处理。
- 非阻塞I/O:Node.js利用操作系统提供的异步I/O接口,使得在等待I/O操作完成时,事件循环可以继续处理其他任务,实现高并发能力。
事件循环是Node.js性能和可扩展性的基石,理解其工作原理对于编写高效、响应式的Node.js应用至关重要。