0

0

深入了解Node事件循环(EventLoop)机制

青灯夜游

青灯夜游

发布时间:2023-03-16 20:11:32

|

2196人浏览过

|

来源于掘金社区

转载

主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为event loop(事件循环)。下面本篇文章就来带大家掌握node.js中的eventloop,希望对大家有所帮助!

深入了解Node事件循环(EventLoop)机制

虽然js可以在浏览器中执行又可以在node中执行,但是它们的事件循环机制并不是一样的。并且有很大的区别。

EventLoop机制概述

在说Node事件循环机制之前,我们先来讨论两个问题

为什么要学习事件循环机制?

学习事件循环可以让开发者明白JavaScript的运行机制是怎么样的。

事件循环机制做的是什么事情?

事件循环机制用于管理异步API的回调函数什么时候回到主线程中执行

Node.js采用的是异步IO模型。同步API在主线程中执行,异步API在底层的C++维护的线程中执行,异步API的回调函数也会在主线程中执行。【相关教程推荐:nodejs视频教程编程教学

在Javascript应用运行时,众多异步API的回调函数什么时候能回到主线程中调用呢?这就是事件环环机制做的事情,管理异步API的回调函数什么时候回到主线程中执行。

EventLoop的六个阶段

Node中的事件循环分为六个阶段。

在事件循环中的每个阶段都有一个队列,存储要执行的回调函数,事件循环机制会按照先进先出的方式执行他们直到队列为空。

这六个阶段都存储着异步回调函数,所以还是遵循先执行主线程同步代码,当同步代码执行完后再来轮询这六个阶段。

接下来,我们来详细看看这六个阶段里面存储的都是什么

Timers

Timers:用于存储定时器的回调函数(setlnterval,setTimeout)。

Pendingcallbacks

Pendingcallbacks:执行与操作系统相关的回调函数,比如启动服务器端应用时监听端口操作的回调函数就在这里调用。

idle,prepare

idle,prepare:系统内部使用。(这个我们程序员不用管)

Poll

Poll:存储1/O操作的回调函数队列,比如文件读写操作的回调函数。

在这个阶段需要特别注意,如果事件队列中有回调函数,则执行它们直到清空队列 ,否则事件循环将在此阶段停留一段时间以等待新的回调函数进入。

但是对于这个等待并不是一定的,而是取决于以下两个条件:

  • 如果setlmmediate队列(check阶段)中存在要执行的调函数。这种情况就不会等待。
  • timers队列中存在要执行的回调函数,在这种情况下也不会等待。事件循环将移至check阶段,然后移至Closingcallbacks阶段,并最终从timers阶段进入下一次循环。

Check

Check:存储setlmmediate的回调函数。

Closingcallbacks

Closingcallbacks:执行与关闭事件相关的回调,例如关闭数据库连接的回调函数等。

宏任务与微任务

跟浏览器中的js一样,node中的异步代码也分为宏任务和微任务,只是它们之间的执行顺序有所区别。

我们再来看看Node中都有哪些宏任务和微任务

宏任务

  • setlnterval

  • setimeout

  • setlmmediate

  • I/O

微任务

  • Promise.then

  • Promise.catch

  • Promise.finally

  • process.nextTick

node中,对于微任务和宏任务的执行顺序到底是怎样的呢?

微任务和宏任务的执行顺序

node中,微任务的回调函数被放置在微任务队列中,宏任务的回调函数被放置在宏任务队列中。

微任务优先级高于宏任务。当微任务事件队列中存在可以执行的回调函数时,事件循环在执行完当前阶段的回调函数后会暂停进入事件循环的下一个阶段,而会立即进入微任务的事件队列中开始执行回调函数,当微任务队列中的回调函数执行完成后,事件循环才会进入到下一个段开始执行回调函数。

对于微任务我们还有个点需要特别注意。那就是虽然nextTick同属于微任务,但是它的优先级是高于其它微任务,在执行微任务时,只有nextlick中的所有回调函数执行完成后才会开始执行其它微任务。

总的来说就是当主线程同步代码执行完毕后会优先清空微任务(如果微任务继续产生微任务则会再次清空),然后再到下个事件循环阶段。并且微任务的执行是穿插在事件循环六个阶段中间的,也就是每次事件循环进入下个阶段前会判断微任务队列是否为空,为空才会进入下个阶段,否则先清空微任务队列。

下面我们用代码实操来验证前面所说的。

代码实例

先执行同步再执行异步

Node应用程序启动后,并不会立即进入事件循环,而是先执行同步代码,从上到下开始执行,同步API立即执行,异步API交给C++维护的线程执行,异步API的回调函数被注册到对应的事件队列中。当所有同步代码执行完成后,才会进入事件循环。

console.log("start");

setTimeout(() => {
  console.log("setTimeout 1");
});

setTimeout(() => {
  console.log("setTimeout 2");
});

console.log("end");

我们来看执行结果

image.png

可以看到,先执行同步代码,然后才会进入事件循环执行异步代码,在timers阶段执行两个setTimeout回调。

setTimeout一定会先于setImmediate执行吗

我们知道setTimeout是在timers阶段执行,setImmediate是在check阶段执行。并且事件循环是从timers阶段开始的。所以会先执行setTimeout再执行setImmediate

对于上面的分析一定对吗?

阿里云AI平台
阿里云AI平台

阿里云AI平台

下载

我们来看例子

console.log("start");

setTimeout(() => {
  console.log("setTimeout");
});

setImmediate(() => {
  console.log("setImmediate");
});

const sleep = (delay) => {
  const startTime = +new Date();
  while (+new Date() - startTime < delay) {
    continue;
  }
};

sleep(2000);
console.log("end");

执行上面的代码,输出如下

image.png

先执行setTimeout再执行setImmediate

接下来我们来改造下上面的代码,把延迟器去掉,看看会输出什么

setTimeout(() => {
  console.log("setTimeout");
});

setImmediate(() => {
  console.log("setImmediate");
});

我们运行了七次,可以看到其中有两次是先运行的setImmediate

image.png

怎么回事呢?不是先timers阶段再到check阶段吗?怎么会变呢?

其实这就得看进入事件循环的时候,异步回调有没有完全准备好了。对于最开始的例子,因为有2000毫秒的延迟,所以进入事件循环的时候,setTimeout回调是一定准备好了的。所以执行顺序不会变。但是对于这个例子,因为主线程没有同步代码需要执行,所以一开始就进入事件循环,但是在进入事件循环的时候,setTimeout的回调并不是一定完全准备好的,所以就会有先到check阶段执行setImmediate回调函数,再到下一次事件循环的timers阶段来执行setTimeout的回调。

那在什么情况下同样的延迟时间,setImmediate回调函数一定会优先于setTimeout的回调呢?

其实很简单,只要将这两者放到timers阶段和check阶段之间的Pendingcallbacks、idle,prepare、poll阶段中任意一个阶段就可以了。因为这些阶段完执行完是一定会先到check再到timers阶段的。

我们以poll阶段为例,将这两者写在IO操作中。

const fs = require("fs");

fs.readFile("./fstest.js", "utf8", (err, data) => {
  setTimeout(() => {
    console.log("setTimeout");
  });

  setImmediate(() => {
    console.log("setImmediate");
  });
});

我们也来执行七次,可以看到,每次都是setImmediate先执行。

image.png

所以总的来说,同样的延迟时间,setTimeout并不是百分百先于setImmediate执行。

先微任务再宏任务

主线程同步代码执行完毕后,会先执行微任务再执行宏任务。

我们来看下面的例子

console.log("start");

setTimeout(() => {
  console.log("setTimeout");
});

setImmediate(() => {
  console.log("setImmediate");
});

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

console.log("end");

我们运行一下看结果,可以看到它是先执行了微任务然后再执行宏任务

image.png

nextTick优于其它微任务

在微任务中nextTick的优先级是最高的。

我们来看下面的例子

console.log("start");

setTimeout(() => {
  console.log("setTimeout");
});

setImmediate(() => {
  console.log("setImmediate");
});

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

process.nextTick(() => {
  console.log("process.nextTick");
});

console.log("end");

我们运行上面的代码,可以看到就算nextTick定义在resolve后面,它也是先执行的。

image.png

微任务穿插在各个阶段间执行

怎么理解这个穿插呢?其实就是在事件循环的六个阶段每个阶段执行完后会清空微任务队列。

我们来看例子,我们建立了timers、check、poll三个阶段,并且每个阶段都产生了微任务。

// timers阶段
setTimeout(() => {
  console.log("setTimeout");

  Promise.resolve().then(() => {
    console.log("setTimeout Promise.resolve");
  });
});

// check阶段
setImmediate(() => {
  console.log("setImmediate");
  Promise.resolve().then(() => {
    console.log("setImmediate Promise.resolve");
  });
});

// 微任务
Promise.resolve().then(() => {
  console.log("Promise.resolve");
});

// 微任务
process.nextTick(() => {
  console.log("process.nextTick");
  Promise.resolve().then(() => {
    console.log("nextTick Promise.resolve");
  });
});

我们来执行上面的代码

image.png

可以看到,先执行微任务,再执行宏任务。先process.nextTick -> Promise.resolve。并且如果微任务继续产生微任务则会再次清空,所以就又输出了nextTick Promise.resolve

接下来到timer阶段,输出setTimeout,并且产生了一个微任务,再进入到下个阶段前需要清空微任务队列,所以继续输出setTimeout Promise.resolve

接下来到check阶段,输出setImmediate,并且产生了一个微任务,再进入到下个阶段前需要清空微任务队列,所以继续输出setImmediate Promise.resolve

这也就印证了微任务会穿插在各个阶段之间运行。

image.png

总结

所以对于Node中的事件循环你只需要背好一以下几点就可以了

  • 当主线程同步代码执行完毕后才会进入事件循环

  • 事件循环总共分六个阶段,并且每个阶段都包括哪些回调需要记清楚。

  • 事件循环中会先执行微任务再执行宏任务。

  • 微任务会穿插在这六个阶段之间执行,每进入到下个阶段前会清空当前的微任务队列。

  • 微任务中process.nextTick的优先级最高,会优先执行。

更多node相关知识,请访问:nodejs 教程

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
node.js调试
node.js调试

node.js调试可以使用console.log()输出调试信息、断点调试和第三方调试工具。详细介绍:1、console.log()输出调试信息,通过在代码中插入console.log()语句,开发人员可以在控制台输出变量的值、函数的执行结果等信息,以便观察代码的执行流程和状态;2、断点调试,可以在代码中设置断点,以便在特定位置暂停代码的执行,观察变量的值和执行流程等。

362

2023.09.19

JavaScript 全栈开发基础(Node.js + 前端)
JavaScript 全栈开发基础(Node.js + 前端)

本专题系统介绍 JavaScript 在全栈开发中的核心知识结构,涵盖 Node.js 基础、Express/Koa 接口构建、前端交互设计、模块化与包管理、数据库连接、前后端数据通信与部署流程。通过完整项目示例,帮助学习者掌握从浏览器到服务器的一体化开发能力,实现真正意义上的全栈入门。

118

2025.11.26

Node.js后端开发与Express框架实践
Node.js后端开发与Express框架实践

本专题针对初中级 Node.js 开发者,系统讲解如何使用 Express 框架搭建高性能后端服务。内容包括路由设计、中间件开发、数据库集成、API 安全与异常处理,以及 RESTful API 的设计与优化。通过实际项目演示,帮助开发者快速掌握 Node.js 后端开发流程。

424

2026.02.10

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

765

2023.08.10

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

765

2023.08.10

js正则表达式
js正则表达式

php中文网为大家提供各种js正则表达式语法大全以及各种js正则表达式使用的方法,还有更多js正则表达式的相关文章、相关下载、相关课程,供大家免费下载体验。

531

2023.06.20

js获取当前时间
js获取当前时间

JS全称JavaScript,是一种具有函数优先的轻量级,解释型或即时编译型的编程语言;它是一种属于网络的高级脚本语言,主要用于Web,常用来为网页添加各式各样的动态功能。js怎么获取当前时间呢?php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

576

2023.07.28

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

761

2023.08.03

TypeScript类型系统进阶与大型前端项目实践
TypeScript类型系统进阶与大型前端项目实践

本专题围绕 TypeScript 在大型前端项目中的应用展开,深入讲解类型系统设计与工程化开发方法。内容包括泛型与高级类型、类型推断机制、声明文件编写、模块化结构设计以及代码规范管理。通过真实项目案例分析,帮助开发者构建类型安全、结构清晰、易维护的前端工程体系,提高团队协作效率与代码质量。

26

2026.03.13

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 6万人学习

TypeScript 教程
TypeScript 教程

共19课时 | 3.4万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 3.6万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号