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 相关文章推荐
iPhone手机上搭建nodejs服务器步骤方法
Jul 06 NodeJs
Nodejs中的this详解
Mar 26 NodeJs
详谈Angular路由与Nodejs路由的区别
Mar 05 NodeJs
nodejs爬虫遇到的乱码问题汇总
Apr 07 NodeJs
nodejs6下使用koa2框架实例
May 18 NodeJs
nodejs实现的连接MySQL数据库功能示例
Jan 25 NodeJs
详解Nodejs mongoose
Jun 10 NodeJs
NodeJS 实现多语言的示例代码
Sep 11 NodeJs
Nodejs调用Dll模块的方法
Sep 17 NodeJs
nodejs遍历文件夹下并操作HTML/CSS/JS/PNG/JPG的方法
Nov 01 NodeJs
nodejs 递归拷贝、读取目录下所有文件和目录
Jul 18 NodeJs
详解利用nodejs对本地json文件进行增删改查
Sep 20 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 CURL用法的深入分析
2013/06/09 PHP
高性能PHP框架Symfony2经典入门教程
2014/07/08 PHP
php防止sql注入简单分析
2015/03/18 PHP
PHP多线程模拟实现秒杀抢单
2018/02/07 PHP
PHP赋值的内部是如何跑的详解
2019/01/13 PHP
javascript 不间断的图片滚动并可点击
2010/01/15 Javascript
JS 文件大小判断的实现代码
2010/04/07 Javascript
jquery里的正则表达式说明
2011/08/03 Javascript
基于JavaScript 声明全局变量的三种方式详解
2013/05/07 Javascript
CSS鼠标响应事件经过、移动、点击示例介绍
2013/09/04 Javascript
jQuery实现弹出窗口中切换登录与注册表单
2015/06/05 Javascript
Web前端新人笔记之jquery入门心得(新手必看)
2016/05/17 Javascript
详解angular应用容器化部署
2018/08/14 Javascript
JS数组的高级使用方法示例小结
2020/03/14 Javascript
Python lxml模块安装教程
2015/06/02 Python
简要讲解Python编程中线程的创建与锁的使用
2016/02/28 Python
PyQt5打开文件对话框QFileDialog实例代码
2018/02/07 Python
Python XML转Json之XML2Dict的使用方法
2019/01/15 Python
详解纯CSS3制作的20种loading动效
2017/07/05 HTML / CSS
css3 响应式媒体查询的示例代码
2019/09/25 HTML / CSS
HTML5 Web Database 数据库的SQL语句的使用方法
2012/12/09 HTML / CSS
html5读取本地文件示例代码
2014/04/22 HTML / CSS
伦敦一家非常流行的时尚精品店:Oxygen Boutique
2017/01/15 全球购物
来自南加州灵感的工作和娱乐服装:TravisMathew
2019/05/01 全球购物
Android笔试题总结
2014/11/29 面试题
一些Solaris面试题
2013/03/22 面试题
工商管理本科毕业生求职信范文
2013/10/05 职场文书
综治维稳工作承诺书
2014/08/30 职场文书
民间个人借款协议书
2014/09/30 职场文书
单位介绍信格式
2015/01/31 职场文书
2015国庆66周年宣传语
2015/07/14 职场文书
员工考勤管理制度
2015/08/06 职场文书
创业计划书之家教托管
2019/09/25 职场文书
Python如何使用logging为Flask增加logid
2021/03/30 Python
Javascript之datagrid查询详解
2021/09/15 Javascript
讲解MySQL增删改操作
2022/05/06 MySQL