了解 Node.js 事件循环:异步编程的核心

126 阅读3分钟

Node.js 作为一个高效的 JavaScript 运行时,其非阻塞的 I/O 模型和事件驱动的架构使得它非常适合处理大量的并发请求。Node.js 事件循环(Event Loop)是它能够高效处理异步操作的核心机制。理解事件循环的工作原理,对于编写高效、可扩展的 Node.js 应用至关重要。

什么是事件循环?

事件循环是 Node.js 处理异步操作的机制。它使得 Node.js 在执行 I/O 操作(如文件读取、数据库查询、网络请求等)时,不会阻塞主线程。相反,Node.js 会将这些操作委托给操作系统或底层线程池,继续执行其他任务。当 I/O 操作完成时,相关的回调函数会被放入事件队列,等待主线程空闲时执行。

简而言之,事件循环使得 Node.js 可以在一个单线程中高效地处理大量并发操作。

事件循环的工作原理

Node.js 的事件循环可以分为几个阶段,每个阶段都有不同的任务要处理。事件循环的基本流程如下:

  1. 检查栈:执行当前的同步代码。如果执行完所有同步代码后,栈为空,事件循环会进入下一阶段。
  2. 定时器(Timers):检查是否有定时器(setTimeoutsetInterval)的回调需要执行。如果定时器的延迟时间已到,则执行相应的回调函数。
  3. I/O 事件(I/O callbacks):处理操作系统通知的 I/O 事件回调。例如,文件读写完成,网络请求完成时的回调。
  4. 闲置或准备(Idle, prepare):内部阶段,Node.js 会执行一些准备工作,通常对开发者不可见。
  5. 轮询(Poll):检查事件队列中是否有待处理的事件。如果队列中有事件,则执行相应的回调。如果没有事件,事件循环会根据需要进行等待或进入下一个阶段。
  6. 检查(Check):执行 setImmediate 注册的回调,这些回调会在当前事件循环的尾部执行。
  7. 关闭(Close callbacks):处理关闭事件回调,比如 socket.on('close')fs.close() 等。

微任务与宏任务

在Node.js的事件循环中,微任务和宏任务是两个重要的概念。微任务如Promiseprocess.nextTick优先级高于宏任务(如setTimeoutsetImmediate),即它们会先被处理。

微任务通常用于处理一些需要在当前任务完成后立即执行的任务,如异步操作的回调函数。而宏任务则用于处理一些需要在下一个事件循环周期中执行的任务,如定时器回调函数。

一个简单的例子

来看一个简单的例子,帮助大家理解事件循环的工作:

console.log('Start');

setTimeout(() => {
    console.log('Timeout 1');
}, 0);

Promise.resolve().then(() => {
    console.log('Promise 1');
});

console.log('End');

执行过程

  1. 同步代码:立即执行。

首先执行 console.log('Start'),然后执行 console.log('End'),这两行是同步的,立即打印到控制台。

  1. 微任务(Promise):在当前事件循环的末尾,但在下一个宏任务之前执行。

接下来,Promise.resolve().then() 的回调会被放到微任务队列中。微任务队列的优先级高于宏任务(如定时器回调),所以 console.log('Promise 1') 会在定时器回调之前执行。

  1. 宏任务(setTimeout):等待当前事件循环的所有任务完成后执行。

虽然 setTimeout 的延时是 0 毫秒,但它是一个宏任务,所以 console.log('Timeout 1') 会在微任务队列中的 Promise 回调之后执行。

最终输出:

Start
End
Promise 1
Timeout 1