首页 > web前端 > js教程 > 正文

Node JS - 事件循环

Susan Sarandon
发布: 2024-11-20 19:03:16
原创
409 人浏览过

Node JS - The Event Loop

我们在名为“Node Internals”的文章中讨论了为什么 Node JS 是单线程的,也是多线程的。它将为您提供 Node 架构的坚实基础,并为理解事件循环的魔力奠定基础!

由于事件循环,Node js 可以被认为是单线程的。但是,什么是事件循环?

我总是从餐厅的类比开始,因为我认为这样更容易理解技术细节。

所以,在餐厅里,主厨从订单列表中取出订单并将其交给助理团队。食物准备好后,厨师就会上菜。如果有VIP顾客来,厨师会优先处理这个订单。

如果我们考虑这个类比,那么我们可以说......

在 Node JS 事件循环的上下文中。

  • Chef 是管理任务和委派工作的事件循环。

  • 协助团队是一个工作线程或操作系统,负责处理委托给他们的任务的执行。

  • 订单列表是等待轮到的任务的任务队列。

  • VIP客户是一个微任务,优先级高,先于常规任务完成。

要了解事件循环,我们必须首先了解微任务和宏任务之间的区别。

微任务

微任务是指具有高优先级的任务,并且在当前执行的 Javascript 代码完成之后、进入事件循环的下一阶段之前执行。

示例:

  • process.nextTick
  • 承诺(.then、.catch、.finally)
  • 队列微任务

宏任务

这些是优先级较低的任务,在事件循环的稍后阶段排队等待执行。

示例:

  • 设置超时
  • 设置间隔
  • 立即设置
  • I/O 操作

事件循环

当我们在 Node.js 中运行异步任务时,事件循环是一切的核心。

得益于事件循环,Node.js 可以高效地执行非阻塞 I/O 操作。它通过将耗时的任务委托给操作系统或工作线程来实现这一点。一旦任务完成,它们的回调就会以有组织的方式处理,确保顺利执行而不阻塞主线程。

这就是 Node.js 能够同时处理多个任务,同时仍然是单线程的神奇之处。

阶段

事件循环中有六个阶段,每个阶段都有自己的队列,其中保存特定类型的任务。

1.计时器阶段

在此阶段处理与计时器相关的回调,例如 setTimeout 和 setInterval。

Node js 检查计时器队列中是否有延迟已过期的回调。

如果满足计时器延迟,其回调将添加到此队列中执行。

console.log('Start');

setTimeout(() => {
  console.log('Timer 1 executed after 1 second');
}, 1000);

setTimeout(() => {
  console.log('Timer 2 executed after 0.5 seconds');
}, 500);

let count = 0;
const intervalId = setInterval(() => {
  console.log('Interval callback executed');
  count++;

  if (count === 3) {
    clearInterval(intervalId);
    console.log('Interval cleared');
  }
}, 1000);

console.log('End');
登录后复制
登录后复制

输出:

Start
End
Timer 2 executed after 0.5 seconds
Timer 1 executed after 1 second
Interval callback executed
Interval callback executed
Interval callback executed
Interval cleared
登录后复制
登录后复制

2.I/O回调阶段

此阶段的目的是为已完成的 I/O(输入/输出)操作执行回调,例如读取或写入文件、查询数据库、处理网络请求以及其他异步 I/O 任务。

当 Node.js 中发生任何异步 I/O 操作(例如使用 fs.readFile 读取文件)时,该操作将委托给操作系统或工作线程。这些 I/O 任务在主线程之外以非阻塞方式执行。任务完成后,会触发回调函数来处理结果。

I/O 回调阶段是操作完成后这些回调排队等待执行的阶段。

const fs = require('fs');

console.log('Start');

fs.readFile('example.txt', 'utf8', (err, data) => {
  if (err) {
    console.log('Error reading file:', err);
    return;
  }
  console.log('File contents:', data);
});

console.log('Middle');

setTimeout(() => {
  console.log('Simulated network request completed');
}, 0);

console.log('End');
登录后复制
登录后复制

输出

Start
Middle
End
Simulated network request completed
File contents: (contents of the example.txt file)
登录后复制
登录后复制

3.空闲阶段

在此阶段,不会执行任何用户定义的工作,而是在此阶段事件循环为下一阶段做好准备。此阶段仅进行内部调整。

4.投票阶段

轮询阶段检查是否有需要处理的待处理 I/O 事件(如网络活动或文件系统事件)。它将立即执行与这些事件相关的回调。

如果没有待处理的 I/O 事件,则轮询阶段可以进入阻塞状态。

在这种阻塞状态下,Node.js 将只是等待新的 I/O 事件到达。这种阻塞状态使 Node.js 成为非阻塞:它会等待,直到新的 I/O 事件触发回调执行,同时保持主线程空闲以执行其他任务。

已完成的 I/O 操作(例如 fs.readFile、HTTP 请求或数据库查询)的任何回调都会在此阶段执行。这些 I/O 操作可能已在之前的阶段(例如计时器阶段或 I/O 回调阶段)启动,现在已完成。

如果有使用 setTimeout 或 setInterval 设置的计时器,Node.js 将检查是否有计时器已过期以及是否需要执行其关联的回调。如果计时器已过期,它们的回调将移至回调队列,但直到下一阶段(即计时器阶段)才会处理它们。

const fs = require('fs');
const https = require('https');

console.log('Start');

fs.readFile('file1.txt', 'utf8', (err, data) => {
  if (err) {
    console.log('Error reading file1:', err);
    return;
  }
  console.log('File1 content:', data);
});

fs.readFile('file2.txt', 'utf8', (err, data) => {
  if (err) {
    console.log('Error reading file2:', err);
    return;
  }
  console.log('File2 content:', data);
});

https.get('https://jsonplaceholder.typicode.com/todos/1', (response) => {
  let data = '';
  response.on('data', (chunk) => {
    data += chunk;
  });
  response.on('end', () => {
    console.log('HTTP Response:', data);
  });
});

console.log('End');
登录后复制
登录后复制

输出:

Start
End
File1 content: (contents of file1.txt)
File2 content: (contents of file2.txt)
HTTP Response: (JSON data from the HTTP request)
登录后复制
登录后复制

5.检查相位

投票阶段完成任务后。该阶段主要处理 setImmediate 回调的执行,这些回调被安排在轮询阶段处理完 I/O 事件后立即运行。

当您想要在当前事件循环周期之后执行某个操作时,通常会使用 setImmediate 回调,例如确保系统不忙于处理 I/O 事件后执行某些任务。

检查阶段的优先级高于定时器阶段(处理 setTimeout 和 setInterval)。这意味着 setImmediate 回调将始终在任何计时器之前执行,即使计时器已过期。

setImmediate 保证其回调将在当前 I/O 周期之后、下一个计时器周期之前运行。当您想要确保在运行其他任务之前先完成 I/O 相关任务时,这一点非常重要。

console.log('Start');

setTimeout(() => {
  console.log('Timer 1 executed after 1 second');
}, 1000);

setTimeout(() => {
  console.log('Timer 2 executed after 0.5 seconds');
}, 500);

let count = 0;
const intervalId = setInterval(() => {
  console.log('Interval callback executed');
  count++;

  if (count === 3) {
    clearInterval(intervalId);
    console.log('Interval cleared');
  }
}, 1000);

console.log('End');
登录后复制
登录后复制

输出:

Start
End
Timer 2 executed after 0.5 seconds
Timer 1 executed after 1 second
Interval callback executed
Interval callback executed
Interval callback executed
Interval cleared
登录后复制
登录后复制

6.结束阶段

关闭回调阶段通常在应用程序需要在退出或关闭之前进行清理时执行。

此阶段处理不再需要系统资源(例如网络套接字或文件句柄)时需要执行的事件和任务。

如果没有此阶段,应用程序可能会留下打开的文件句柄、网络连接或其他资源,从而可能导致内存泄漏、数据损坏或其他问题。

const fs = require('fs');

console.log('Start');

fs.readFile('example.txt', 'utf8', (err, data) => {
  if (err) {
    console.log('Error reading file:', err);
    return;
  }
  console.log('File contents:', data);
});

console.log('Middle');

setTimeout(() => {
  console.log('Simulated network request completed');
}, 0);

console.log('End');
登录后复制
登录后复制

输出:

Start
Middle
End
Simulated network request completed
File contents: (contents of the example.txt file)
登录后复制
登录后复制

Node JS 的事件循环中还有一个特殊的阶段。

微任务队列

process.nextTick() 并承诺在事件循环的特殊阶段执行回调。

process.nextTick() 安排回调在当前操作完成后立即执行,但在事件循环继续下一阶段之前。

process.nextTick() 不是事件循环中任何阶段的一部分。相反,它有自己的内部队列,该队列在当前执行的同步代码之后和进入事件循环中的任何阶段之前立即执行。

它在当前操作之后但在 I/O、setTimeout 或事件循环中安排的其他任务之前执行。

Promise 的优先级低于 process.nextTick(),并且在所有 process.nextTick() 回调之后处理。

const fs = require('fs');
const https = require('https');

console.log('Start');

fs.readFile('file1.txt', 'utf8', (err, data) => {
  if (err) {
    console.log('Error reading file1:', err);
    return;
  }
  console.log('File1 content:', data);
});

fs.readFile('file2.txt', 'utf8', (err, data) => {
  if (err) {
    console.log('Error reading file2:', err);
    return;
  }
  console.log('File2 content:', data);
});

https.get('https://jsonplaceholder.typicode.com/todos/1', (response) => {
  let data = '';
  response.on('data', (chunk) => {
    data += chunk;
  });
  response.on('end', () => {
    console.log('HTTP Response:', data);
  });
});

console.log('End');
登录后复制
登录后复制

输出:

Start
End
File1 content: (contents of file1.txt)
File2 content: (contents of file2.txt)
HTTP Response: (JSON data from the HTTP request)
登录后复制
登录后复制

现在,您对事件循环的工作原理有了总体了解。

我给你一个问题,你可以在评论中给出答案。

const fs = require('fs');

console.log('Start');

fs.readFile('somefile.txt', 'utf8', (err, data) => {
  if (err) {
    console.error(err);
    return;
  }
  console.log('File content:', data);
});

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

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

console.log('End');
登录后复制

谢谢。

等待您的答复。

以上是Node JS - 事件循环的详细内容。更多信息请关注PHP中文网其他相关文章!

来源:dev.to
本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
作者最新文章
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板