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 Express框架中处理404页面一个方式
May 28 NodeJs
nodejs文件操作模块FS(File System)常用函数简明总结
Jun 05 NodeJs
ubuntu下安装nodejs以及升级的办法
May 08 NodeJs
nodejs导出excel的方法
Jun 30 NodeJs
实例详解Nodejs 保存 payload 发送过来的文件
Jan 14 NodeJs
Nodejs中的this详解
Mar 26 NodeJs
nodeJs内存泄漏问题详解
Sep 05 NodeJs
详解nodejs 文本操作模块-fs模块(四)
Dec 22 NodeJs
Highcharts+NodeJS搭建数据可视化平台示例
Jan 01 NodeJs
Nodejs中的JWT和Session的使用
Aug 21 NodeJs
NodeJs 文件系统操作模块fs使用方法详解
Nov 26 NodeJs
NodeJs入门教程之定时器和队列
Mar 08 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
WordPress判断用户是否登录的代码
2011/03/17 PHP
WIN8.1下搭建PHP5.6环境
2015/04/29 PHP
PHP微信开发之查询城市天气
2016/06/23 PHP
php微信开发之图片回复功能
2018/06/14 PHP
Yii框架中用response保存cookie,用request读取cookie的原理解析
2019/09/04 PHP
iframe自适应宽度、高度 ie6 7 8,firefox 3.86下测试通过
2010/07/29 Javascript
JS自调用匿名函数具体实现
2014/02/11 Javascript
Bootstrap表单布局样式代码
2016/05/31 Javascript
jQuery实现区域打印功能代码详解
2016/06/17 Javascript
Vue.js 表单校验插件
2016/08/14 Javascript
javascript宿主对象之window.navigator详解
2016/09/07 Javascript
通过网页查看JS源码中汉字显示乱码的解决方法
2016/10/26 Javascript
js实现加载更多功能实例
2016/10/27 Javascript
浅谈EasyUi ComBotree树修改 父节点选择的问题
2016/11/07 Javascript
js判断是否是手机页面
2017/03/17 Javascript
JavaScript+HTML5 canvas实现放大镜效果完整示例
2019/05/15 Javascript
详解vue-cli@2.x项目迁移日志
2019/06/06 Javascript
vue滚动插件better-scroll使用详解
2019/10/18 Javascript
详解Vue的watch中的immediate与watch是什么意思
2019/12/30 Javascript
es6函数name属性功能与用法实例分析
2020/04/18 Javascript
python logging日志模块的详解
2017/10/29 Python
Python使用numpy产生正态分布随机数的向量或矩阵操作示例
2018/08/22 Python
Python使用os.listdir()和os.walk()获取文件路径与文件下所有目录的方法
2019/04/01 Python
win10子系统python开发环境准备及kenlm和nltk的使用教程
2019/10/14 Python
python安装读取grib库总结(推荐)
2020/06/24 Python
HTML5本地存储之Database Storage应用介绍
2013/01/06 HTML / CSS
世界上最好的儿童品牌:AlexandAlexa
2018/01/27 全球购物
介绍一些UNIX常用简单命令
2014/11/11 面试题
大三学生做职业规划:给未来找个方向
2014/02/24 职场文书
室内设计专业毕业生求职信
2014/05/02 职场文书
上党课的心得体会
2014/09/02 职场文书
群众路线个人对照检查材料2014
2014/09/26 职场文书
债务纠纷委托书范本
2014/10/14 职场文书
先进工作者事迹材料
2014/12/23 职场文书
委托开发合同书(标准版)
2019/08/07 职场文书
Python获取指定日期是"星期几"的6种方法
2022/03/13 Python