百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT知识 > 正文

关于NodeJS工作原理的五个误解

liuian 2025-03-13 17:16 46 浏览



英文原文:
https://blog.insiderattack.net/five-misconceptions-on-how-nodejs-works-edfb56f7b3a6

授权译者:ConardLi

NodeJS 诞生于 2009 年,由于它使用了 JavaScript,在这些年里获得了非常广泛的流行。它是一个用于编写服务器端应用程序的 JavaScript 运行时,但是 "它就是JavaScript" 这句话并不是 100% 正确的。

JavaScript 是单线程的,它不是被设计用来实现要求可伸缩性的服务器端上运行的。借助 Google Chrome 的高性能 V8 JavaScript 引擎,libuv 的超酷异步 I/O 实现以及其他一些刺激性的补充,NodeJS 能够将客户端 JavaScript 引入服务器端,从而能够编写超快速的、能够处理成千上万的套接字连接的 Web JavaScript 服务器。

如上图所示,NodeJS 是一个由大量有趣的基础模块构建的大型平台。但是,由于对 NodeJS的这些内部组件的工作方式缺乏了解,因此许多 NodeJS 开发人员对 NodeJS 的行为做出了错误的理解,并开发了导致严重性能问题以及难以跟踪的错误的应用程序。在本文中,我将描述在许多 NodeJS 开发人员中很常见的五个错误理解。

误解1 — EventEmitter 和事件循环相关

编写 NodeJS 应用程序时会大量使用 NodeJS EventEmitter,但是人们误认为 EventEmitter与 NodeJS Event Loop 有关,这是不正确的。

NodeJS 事件循环是 NodeJS 的核心,它为 NodeJS 提供了异步的,非阻塞的 I/O 机制。它以特定顺序处理来自不同类型的异步事件的完成事件。

相反,NodeJS Event Emitter 是一个核心的 NodeJS API,它允许你将监听器函数附加到一个特定的事件,这个事件一旦触发就会被调用。这种行为看起来像是异步的,因为事件处理程序的调用时间通常比它最初作为事件处理程序注册的时间晚。

EventEmitter 实例跟踪与 EventEmitter 实例本身内的事件相关联的所有事件和其实例本身。它不会在事件循环队列中调度任何事件。存储此信息的数据结构只是一个普通的老式 JavaScript 对象,其中对象属性是事件名称,属性的值是一个侦听器函数或侦听器函数数组。

当在 EventEmitter 实例上调用 emit 函数时, emitter 将按顺序依次同步调所有注册到示例上的回调函数。

看以下代码片段:

const EventEmitter = require('events');

const myEmitter = new EventEmitter();

myEmitter.on('myevent', () => console.log('handler1: myevent was fired!'));
myEmitter.on('myevent', () => console.log('handler2: myevent was fired!'));
myEmitter.on('myevent', () => console.log('handler3: myevent was fired!'));

myEmitter.emit('myevent');
console.log('I am the last log line');

以上代码段的输出为:

handler1: myevent was fired!
handler2: myevent was fired!
handler3: myevent was fired!
I am the last log line

由于 event emitter 同步执行所有事件处理函数,因此 I am the last log line 在调用所有监听函数完成之后才会打印。

误解2 - 所有接受回调的函数都是异步的

函数是同步的还是异步的取决于函数在执行期间是否创建异步资源。根据这个定义,如果给你一个函数,你可以确定给定的函数是异步的:

  • 调用本地 JavaScript / 异步的 NodeJS 功能(例如,setTimeout,setInterval,setImmediate,process.nextTick,等等)
  • 执行异步的 NodeJS API(例如,异步函数 child_process,fs,net等等) 使用 PromiseAPI(包括使用 async-await )
  • 从 C++ 插件调用一个函数,该函数被编写为异步函数(例如bcrypt)

接受回调函数作为参数不会使函数异步。但是,通常异步函数的确接受回调作为最后一个参数(除非包装返回一个 Promise )。接受回调并将结果传递给回调的这种模式称为Continuation Passing Style。你仍然可以使用 Continuation Passing Style 编写同步功能。

const sum = (a, b, callback) => {
  callback(a + b);
};

sum(1,2, (result) => {
  console.log(result);
});

同步函数和异步函数在执行期间在如何使用堆栈方面有很大的不同。同步函数在执行的整个过程中都会占用堆栈,方法是禁止其他任何人占用堆栈直到return 为止。相反,异步函数调度一些异步任务并立即返回,因此将自身从堆栈中删除。一旦预定的异步任务完成,将调用提供的任何回调,并且该回调函数将再次占据该堆栈。此时,启动异步任务的函数将不再可用,因为它已经返回。

考虑到以上定义,请尝试确定以下函数是异步还是同步。

function writeToMyFile(data, callback) {
    if (!data) {
        callback(new Error('No data provided'));
    } else {
        fs.writeFile('myfile.txt', data, callback);
    }
}

实际上,上述函数可以是同步的,也可以是异步的,具体取决于传递给的值 data。

如果 data 为 false, callback 则将立即调用,并出现错误。在此执行路径中,该功能是 100% 同步的,因为它不执行任何异步任务。

如果 data 是 true ,它会将 data 写入 myfile.txt,将调用回调完成的文件 I/O 操作之后。由于异步文件 I/O 操作,此执行路径是100%异步的。

强烈建议不要以这种不一致的方式(在此功能同时执行同步和异步操作)编写函数,因为这会使应用程序的行为无法预测。幸运的是,这些不一致可以很容易地修复如下:

function writeToMyFile(data, callback) {
    if (!data) {
        process.nextTick(() => callback(new Error('No data provided')));
    } else {
        fs.writeFile('myfile.txt', data, callback);
    }
}

process.nextTick 可以用来延迟 callback 函数的调用,从而使执行路径异步。

或者,你可以使用 setImmediate 代替 process.nextTick ,这或多或少会产生相同的结果。但是,process.nextTick相对而言,回调具有更高的优先级,从而使其比 setImmediate 更快。

误解3 - 所有占用大量CPU的功能都在阻止事件循环

众所周知, CPU 密集型操作会阻塞 Node.js 事件循环。尽管这句话在一定程度上是正确的,但并不是100%正确,因为有些 CPU 密集型函数不会阻塞事件循环。

一般来说,加密操作和压缩操作是受 CPU 高度限制的。由于这个原因,某些加密函数和zlib 函数的异步版本以在 libuv 线程池上执行计算的方式编写,这样它们就不会阻塞事件循环。其中一些功能是:

  • crypto.pbkdf2()
  • crypto.randomFill()
  • crypto.randomBytes()
  • 所有 zlib 异步功能

但是,在撰写本文时,还无法使用纯 JavaScript 在 libuv 线程池上运行CPU密集型操作。但是,你可以编写自己的 C++ 插件,使你能够安排 libuv 线程池上的工作。有某些第三方库(例如 bcrypt),它们执行CPU密集型操作并使用 C++ 插件来实现针对CPU绑定操作的异步API。

误解4 - 所有异步操作都在线程池上执行

现代操作系统具有内置的内核支持,可使用事件通知(例如,Linux 中的 epoll , macOS中的 kqueue,Windows 中的 IOCP 等)以有效的方式促进网络I/O 操作的本机异步。因此,不会在 libuv 线程池上执行网络 I/O。

但是,当涉及到文件 I/O 时,跨操作系统以及同一操作系统中的某些情况存在许多不一致之处。这使得为文件 I/O 实现通用的独立于平台的 API 极为困难。因此,在 libuv 线程池上执行文件系统操作以公开一致的异步 API。

dns.lookup() dns 模块中的函数是另一个利用 libuv 线程池的API。原因是,使用 dns.lookup() 功能将域名解析为IP地址是与平台有关的操作,并且此操作不是100% 的网络 I/O。

误解5 - 不应使用NodeJS编写CPU密集型应用程序

这并不是真正的误解,而是关于 NodeJS 的一个众所周知的事实,现在由于在 Node v10.5.0中引入 Worker Threads 而被淘汰了。尽管它是作为实验性功能引入的,但 worker_threads自 Node v12 LTS 起,该模块现已稳定,因此适合在具有CPU密集型操作的生产应用程序中使用。

每个 Node.js 工作线程将拥有其自己的v8运行时的副本,事件循环和 libuv 线程池。因此,执行阻塞CPU密集型操作的一个工作线程不会影响其他工作线程的事件循环,从而使它们可用于任何传入的工作。

但是,在撰写本文时,IDE对 Worker Threads 的支持还不是最大。某些IDE不支持将调试器附加到在主线程以外的其他线程中运行的代码。但是,随着许多开发人员已经开始采用辅助线程进行CPU绑定的操作(例如视频编码等),开发支持将随着时间的推移而成熟。

原地址:
https://mp.weixin.qq.com/s/hAE3ket5N_vC-OlV0ISAzQ

相关推荐

驱动网卡(怎么从新驱动网卡)
驱动网卡(怎么从新驱动网卡)

网卡一般是指为电脑主机提供有线无线网络功能的适配器。而网卡驱动指的就是电脑连接识别这些网卡型号的桥梁。网卡只有打上了网卡驱动才能正常使用。并不是说所有的网卡一插到电脑上面就能进行数据传输了,他都需要里面芯片组的驱动文件才能支持他进行数据传输...

2026-01-30 00:37 liuian

win10更新助手装系统(微软win10更新助手)

1、点击首页“系统升级”的按钮,给出弹框,告诉用户需要上传IMEI码才能使用升级服务。同时给出同意和取消按钮。华为手机助手2、点击同意,则进入到“系统升级”功能华为手机助手华为手机助手3、在检测界面,...

windows11专业版密钥最新(windows11专业版激活码永久)

 Windows11专业版的正版密钥,我们是对windows的激活所必备的工具。该密钥我们可以通过微软商城或者通过计算机的硬件供应商去购买获得。获得了windows11专业版的正版密钥后,我...

手机删过的软件恢复(手机删除过的软件怎么恢复)
手机删过的软件恢复(手机删除过的软件怎么恢复)

操作步骤:1、首先,我们需要先打开手机。然后在许多图标中找到带有[文件管理]文本的图标,然后单击“文件管理”进入页面。2、进入页面后,我们将在顶部看到一行文本:手机,最新信息,文档,视频,图片,音乐,收藏,最后是我们正在寻找的[更多],单击...

2026-01-29 23:55 liuian

一键ghost手动备份系统步骤(一键ghost 备份)

  步骤1、首先把装有一键GHOST装系统的U盘插在电脑上,然后打开电脑马上按F2或DEL键入BIOS界面,然后就选择BOOT打USDHDD模式选择好,然后按F10键保存,电脑就会马上重启。  步骤...

怎么创建局域网(怎么创建局域网打游戏)

  1、购买路由器一台。进入路由器把dhcp功能打开  2、购买一台交换机。从路由器lan端口拉出一条网线查到交换机的任意一个端口上。  3、两台以上电脑。从交换机任意端口拉出网线插到电脑上(电脑设置...

精灵驱动器官方下载(精灵驱动手机版下载)

是的。驱动精灵是一款集驱动管理和硬件检测于一体的、专业级的驱动管理和维护工具。驱动精灵为用户提供驱动备份、恢复、安装、删除、在线更新等实用功能。1、全新驱动精灵2012引擎,大幅提升硬件和驱动辨识能力...

一键还原系统步骤(一键还原系统有哪些)

1、首先需要下载安装一下Windows一键还原程序,在安装程序窗口中,点击“下一步”,弹出“用户许可协议”窗口,选择“我同意该许可协议的条款”,并点击“下一步”。  2、在弹出的“准备安装”窗口中,可...

电脑加速器哪个好(电脑加速器哪款好)

我认为pp加速器最好用,飞速土豆太懒,急速酷六根本不工作。pp加速器什么网页都加速,太任劳任怨了!以上是个人观点,具体性能请自己试。ps:我家电脑性能很好。迅游加速盒子是可以加速电脑的。因为有过之...

任何u盘都可以做启动盘吗(u盘必须做成启动盘才能装系统吗)

是的,需要注意,U盘的大小要在4G以上,最好是8G以上,因为启动盘里面需要装系统,内存小的话,不能用来安装系统。内存卡或者U盘或者移动硬盘都可以用来做启动盘安装系统。普通的U盘就可以,不过最好U盘...

u盘怎么恢复文件(u盘文件恢复的方法)

开360安全卫士,点击上面的“功能大全”。点击文件恢复然后点击“数据”下的“文件恢复”功能。选择驱动接着选择需要恢复的驱动,选择接入的U盘。点击开始扫描选好就点击中间的“开始扫描”,开始扫描U盘数据。...

系统虚拟内存太低怎么办(系统虚拟内存占用过高什么原因)

1.检查系统虚拟内存使用情况,如果发现有大量的空闲内存,可以尝试释放一些不必要的进程,以释放内存空间。2.如果系统虚拟内存使用率较高,可以尝试增加系统虚拟内存的大小,以便更多的应用程序可以使用更多...

剪贴板权限设置方法(剪贴板访问权限)
剪贴板权限设置方法(剪贴板访问权限)

1、首先打开iphone手机,触碰并按住单词或图像直到显示选择选项。2、其次,然后选取“拷贝”或“剪贴板”。3、勾选需要的“权限”,最后选择开启,即可完成苹果剪贴板权限设置。仅参考1.打开苹果手机设置按钮,点击【通用】。2.点击【键盘】,再...

2026-01-29 21:37 liuian

平板系统重装大师(平板重装win系统)

如果你的平板开不了机,但可以连接上电脑,那就能好办,楼主下载安装个平板刷机王到你的个人电脑上,然后连接你的平板,平板刷机王会自动识别你的平板,平板刷机王上有你平板的我刷机包,楼主点击下载一个,下载完成...

联想官网售后服务网点(联想官网售后服务热线)

联想3c服务中心是联想旗下的官方售后,是基于互联网O2O模式开发的全新服务平台。可以为终端用户提供多品牌手机、电脑以及其他3C类产品的维修、保养和保险服务。根据客户需求层次,联想服务针对个人及家庭客户...