NodeJS开发人员常见五个错误理解


Posted in NodeJs onOctober 14, 2020

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绑定的操作(例如视频编码等),开发支持将随着时间的推移而成熟。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

NodeJs 相关文章推荐
使用Nodejs开发微信公众号后台服务实例
Sep 03 NodeJs
使用NodeJs 开发微信公众号(三)微信事件交互实例
Mar 02 NodeJs
解决nodejs中使用http请求返回值为html时乱码的问题
Feb 18 NodeJs
详解nodejs爬虫程序解决gbk等中文编码问题
Apr 06 NodeJs
nodejs密码加密中生成随机数的实例代码
Jul 17 NodeJs
nodejs+mongodb aggregate级联查询操作示例
Mar 17 NodeJs
Nodejs中的JWT和Session的使用
Aug 21 NodeJs
NodeJS如何实现同步的方法示例
Aug 24 NodeJs
nodejs初始化init的示例代码
Oct 10 NodeJs
NodeJS使用Range请求实现下载功能的方法示例
Oct 12 NodeJs
nodejs实现聊天机器人功能
Sep 19 NodeJs
使用 Koa + TS + ESLlint 搭建node服务器的过程详解
May 30 NodeJs
浅谈vue websocket nodeJS 进行实时通信踩到的坑
Sep 22 #NodeJs
基于NodeJS开发钉钉回调接口实现AES-CBC加解密
Aug 20 #NodeJs
浅谈使用nodejs搭建web服务器的过程
Jul 20 #NodeJs
通过实例了解Nodejs模块系统及require机制
Jul 16 #NodeJs
Nodejs环境实现socket通信过程解析
Jul 03 #NodeJs
使用nodejs实现JSON文件自动转Excel的工具(推荐)
Jun 24 #NodeJs
nodejs各种姿势断点调试的方法
Jun 18 #NodeJs
You might like
表格展示无限级分类(PHP版)
2012/08/21 PHP
探讨file_get_contents与curl效率及稳定性的分析
2013/06/06 PHP
浅析虚拟主机服务器php fsockopen函数被禁用的解决办法
2013/08/07 PHP
ThinkPHP学习笔记(一)ThinkPHP部署
2014/06/22 PHP
Mootools 1.2教程 正则表达式
2009/09/15 Javascript
javascript与CSS复习(三)
2010/06/29 Javascript
JS过滤url参数特殊字符的实现方法
2013/12/24 Javascript
JavaScript点击按钮后弹出透明浮动层的方法
2015/05/11 Javascript
JS如何判断是否为ie浏览器的方法(包括IE10、IE11在内)
2015/12/13 Javascript
javascript简单比较日期大小的方法
2016/01/05 Javascript
jQuery实现选项联动轮播效果【附实例】
2016/04/19 Javascript
jquery radio的取值_radio的选中_radio的重置方法
2016/09/20 Javascript
js获取隐藏元素的宽高
2017/02/24 Javascript
在nginx上部署vue项目(history模式)的方法
2017/12/28 Javascript
基于JavaScript中标识符的命名规则介绍
2018/01/06 Javascript
Bootstrap treeview实现动态加载数据并添加快捷搜索功能
2018/01/07 Javascript
vue+webpack中配置ESLint
2018/11/07 Javascript
深入解析Python中的WSGI接口
2015/05/11 Python
简单实现python数独游戏
2018/03/30 Python
pandas的object对象转时间对象的方法
2018/04/11 Python
Python中的TCP socket写法示例
2018/05/11 Python
python写入并获取剪切板内容的实例
2018/05/31 Python
Python中Numpy ndarray的使用详解
2019/05/24 Python
seek引发的python文件读写的问题及解决
2019/07/26 Python
python进阶之自定义可迭代的类
2019/08/20 Python
Python使用微信itchat接口实现查看自己微信的信息功能详解
2019/08/22 Python
python实现人机五子棋
2020/03/25 Python
翻转数列python实现,求前n项和,并能输出整个数列的案例
2020/05/03 Python
详解python中的三种命令行模块(sys.argv,argparse,click)
2020/12/15 Python
css实例教程 一款纯css3实现的超炫动画背画特效
2014/11/05 HTML / CSS
详解HTML5新增标签
2017/11/27 HTML / CSS
机械设计及其自动化专业推荐信
2013/10/31 职场文书
运动会解说词200字
2014/02/06 职场文书
中文专业自荐书
2014/06/29 职场文书
田径运动会广播稿
2015/08/19 职场文书
vue3.0 数字翻牌组件的使用方法详解
2022/04/20 Vue.js