重学nodejs系列之web & node eventLoop(一)

引言

nodejs是一个基于事件驱动和非阻塞I/O的js运行时环境。

事件驱动由下面四个部分组成:

  1. 事件(Event): 事件是程序中某一特定瞬间发生的事情,可以是用户的操作、系统状态的变化等。在Node.js中,事件可以是网络请求、文件读取完成等。
  2. 事件监听器(Event Listener): 事件监听器是一个函数,它负责处理特定类型的事件。在Node.js中,监听器通过on方法绑定到特定的事件上。
  3. 触发器(Emitter): 事件的发起者被称为触发器或事件发射器。在Node.js中,事件触发器是一个对象,通过调用特定的方法(比如emit方法)来触发与之相关联的事件。
  4. 回调函数(Callback): 事件处理程序通常是回调函数,它们在相应的事件发生时被异步调用。回调函数负责处理事件,并在事件完成后执行相关的操作。

而非阻塞I/O是一种处理输入输出操作的方式,允许程序在等待数据准备好时继续执行其他任务,而不被阻塞。关键概念包括:

  1. 异步(Asynchronous): 非阻塞I/O操作是异步进行的,不会等待操作完成,而是通过轮询或事件通知的方式检查状态。
  2. 轮询或事件驱动: 程序通过轮询或事件驱动的方式检查I/O操作是否完成,一旦数据准备好,再进行相应的处理。

理解枯燥的概念是无聊的,我们来看看在nodejs中实际的例子

ts 复制代码
import fs from "fs"
import { EventEmitter } from "events"
import path from "path"

// 创建一个事件触发器
const eventEmitter = new EventEmitter();

// 绑定事件监听器
eventEmitter.on('fileRead', (data) => {
  console.log(`文件内容:${data}`);
});

// 触发文件读取事件
fs.readFile(path.join(__dirname, "test.txt"), 'utf8', (err, data) => {
  if (err) throw err;
  
  // 触发事件,并传递读取的数据
  eventEmitter.emit('fileRead', data);
});

console.log('程序继续执行,不会等待文件读取完成。');
/**
程序继续执行,不会等待文件读取完成。
文件内容:事件驱动由下面四个部分组成:

事件(Event): 事件是程序中某一特定瞬间发生的事情,可以是用户的操作、系统状态的变化等。在Node.js中,事件可以是网络请求、文件读取完成等。
事件监听器(Event Listener): 事件监听器是一个函数,它负责处理特定类型的事件。在Node.js中,监听器通过on方法绑定到特定的事件上。
触发器(Emitter): 事件的发起者被称为触发器或事件发射器。在Node.js中,事件触发器是一个对象,通过调用特定的方法(比如emit方法)来触发与之相关联的事件。
回调函数(Callback): 事件处理程序通常是回调函数,它们在相应的事件发生时被异步调用。回调函数负责处理事件,并在事件完成后执行相关的操作。
**/
ts 复制代码
import http from "http"

// 发起一个异步的HTTP请求
const request = http.get('http://www.baidu.com', (response) => {
  let data = '';

  // 当有数据到达时触发data事件
  response.on('data', (chunk) => {
    data += chunk;
  });

  // 当数据接收完成时触发end事件
  response.on('end', () => {
    console.log(`收到的数据:${data}`);
  });
});

// 请求不会阻塞,程序可以继续执行其他任务
console.log('程序继续执行,不会等待HTTP请求完成。');

再谈web eventLoop

因为JS是单线程的,浏览器里面也有一个eventLoop。浏览器要处理用户交互、网络请求、定时器、DOM渲染等事件。事件循环使得浏览器能够同时处理多个任务而不阻塞主线程。 Web Event Loop通常被划分为不同的阶段:

  • 宏任务队列(Macrotask Queue): 包括整体的script代码、setTimeout、setInterval等。
  • 微任务队列(Microtask Queue): 包括Promise、MutationObserver等。

执行顺序简单如下:

  1. 执行同步代码
  2. 执行微任务队列中的所有任务
  3. 执行当前宏任务队列中的一个任务
  4. 重复执行2 3步骤,直至所有队列为空
ts 复制代码
console.log('开始');

setTimeout(() => {
  console.log('setTimeout回调');
}, 0);

const fetchData = () => {
  return new Promise((resolve, reject) => {
    console.log('请求数据');
    setTimeout(() => {
      resolve('请求成功');
    }, 1000);
  });
};

const processPromise = async () => {
  console.log('执行promise之前');
  const result = await fetchData(); // 等待promise被执行成功才执行后面代码,其余任务不会被阻塞
  console.log(result);
  console.log('执行promise之后');
};

processPromise();

console.log('结束');
// 开始 执行promise之前 请求数据 结束 setTimeout回调 请求成功 执行promise之后

好的,简单回顾了一下web eventLoop我们就开始这篇文章的主题了

node eventLoop

nodejs把任务分为大致如下阶段:

  1. Timers(定时器阶段): 处理通过setTimeoutsetInterval设置的定时器回调函数。在这个阶段,Node.js会检查是否有定时器到期,并执行相应的回调。
  2. I/O callbacks(I/O回调阶段): 处理I/O操作的回调函数,比如文件读取、网络请求等。当一个异步I/O操作完成时,其回调会在这个阶段执行。
  3. Idle, prepare(空闲阶段,准备阶段): 这两个阶段通常被忽略,不过Node.js Event Loop在进入下一个阶段之前会执行一些内部操作。
  4. Poll(轮询阶段): 负责处理轮询队列中的事件,比如处理TCP连接、接收新的连接、以及执行setImmediate的回调函数。
  5. Check(检查阶段): 处理通过setImmediate注册的回调函数。在Poll阶段完成后,会执行Check阶段中的回调函数。
  6. Close callbacks(关闭回调阶段): 处理通过socket.on('close', ...)等事件注册的回调函数。在TCP连接关闭时,这个阶段会执行相应的回调。

按宏任务和微任务分:

  • 宏任务队列(Macrotask Queue): setTimeoutsetInterval,I/O 操作的回调,例如文件读取、网络请求,一些特殊的任务,例如 setImmediate 回调。
  • 微任务队列(Microtask Queue): Promise 的回调、process.nextTick 等。

nodejs的执行顺序简单如下:

  1. 先执行同步代码
  2. 执行所有微任务
  3. 执行宏任务队列的一个任务
  4. 重复2 3步骤,直至宏任务队列为空
ts 复制代码
import fs from "fs"
import path from "path"

console.log('开始执行');

// 定时器任务,属于宏任务
setTimeout(() => {
  console.log('Timeout');
}, 0);

// I/O 操作的回调,属于宏任务
fs.readFile(path.join(__dirname, 'test.txt'), 'utf8', (err, data) => {
  console.log('fs回调');
});

// 特殊任务,属于宏任务
setImmediate(() => {
  console.log('Immediate');
});

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

// process.nextTick 回调,属于微任务
process.nextTick(() => {
  console.log('process.nextTick');
});

console.log('结束');
// 开始执行 结束 process.nextTick Promise1 Promise2 Timeout Immediate fs回调

setTimeout & setImmediate & process.nextTick

setTimeoutsetImmediateprocess.nextTick 是 Node.js 中用于处理异步操作的三种机制。它们在事件循环中有不同的执行时机和优先级。

setTimeout:

  • 使用 setTimeout 可以延迟执行一个函数,它将在指定的时间间隔之后添加一个宏任务到宏任务队列。
  • 由于存在最小延迟时间(通常是4毫秒),实际执行时间可能会略有延迟。
ts 复制代码
setTimeout(() => {
  console.log('setTimeout');
}, 1000);

setImmediate:

  • setImmediate 用于在当前事件循环迭代的末尾添加一个宏任务。
  • setImmediate 的回调会在当前宏任务执行完毕后、下一个宏任务执行之前执行。
ts 复制代码
setImmediate(() => {
  console.log('setImmediate');
});

process.nextTick:

  • process.nextTick 会在当前事件循环迭代的末尾立即执行,优先级高于微任务队列中的Promise回调。
  • 适用于确保在下一个事件循环之前执行某些代码。
ts 复制代码
process.nextTick(() => {
  console.log('process.nextTick');
});
ts 复制代码
setTimeout(() => {
  console.log('setTimeout');
}, 1000);

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

process.nextTick(() => {
  console.log('process.nextTick');
});
// process.nextTick setImmediate setTimeout
  • setTimeout 在timer阶段执行。
  • setImmediate 在check阶段执行。
  • process.nextTick 在当前阶段的末尾执行,不属于特定的阶段。

我们来看一道题目

ts 复制代码
console.log("开始");

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

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

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

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

console.log("结束");
// 开始 结束 Next Tick 1 Timeout 1 Immediate 1 Timeout 2
  1. 开始 和 结束 首先被打印,因为它们是同步代码,按照书写顺序执行。
  2. Next Tick 1 在事件循环的下一个循环中被执行,因为 process.nextTick 的回调函数在当前循环结束后立即执行。// 不属于任何阶段
  3. Timeout 1 在下一个循环中被执行,尽管设置了0毫秒的延迟,但由于事件循环的特性,它会在下一个循环中执行。 // timer阶段
  4. Immediate 1 在当前循环的末尾被执行,因为setImmediate的回调函数在当前循环结束时执行。// check阶段
  5. Timeout 2 在由于上述操作而产生的新循环中被执行,由于设置了100毫秒的延迟,因此在 Timeout 1 之后执行。 // timer阶段
相关推荐
崔庆才丨静觅7 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60617 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了8 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅8 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅8 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅8 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment8 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅9 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊9 小时前
jwt介绍
前端
爱敲代码的小鱼9 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax