事件循环是了解节点最重要的方面之一
介绍
这事件循环是有关Node的最重要方面之一。
为什么这个这么重要?因为它说明了Node如何实现异步并具有无阻塞的I / O,所以它基本上说明了Node的“杀手级应用”,这是使它成功的原因。
Node.js JavaScript代码在单个线程上运行。一次只发生一件事。
这个限制实际上非常有用,因为它大大简化了您的编程方式,而无需担心并发问题。
您只需要注意如何编写代码,并避免任何可能阻塞线程的事情,例如同步网络调用或无限循环。
通常,在大多数浏览器中,每个浏览器选项卡都有一个事件循环,以使每个进程都隔离开,并避免出现无限循环或繁重的处理过程的网页来阻塞您的整个浏览器。
该环境管理多个并发事件循环,以处理例如API调用。网络工作者也可以在自己的事件循环中运行。
您主要需要担心的是您的代码将在单个事件循环上运行,并且在编写代码时要牢记这一点,以避免阻塞它。
阻止事件循环
任何花费很长时间才能将控制权返回到事件循环的JavaScript代码,都将阻止页面中任何JavaScript代码的执行,甚至阻止UI线程,并且用户无法单击浏览,滚动页面等。
JavaScript中几乎所有的I / O原语都是非阻塞的。网络请求,文件系统操作等。被阻塞是一个例外,这就是为什么JavaScript如此多地基于回调,最近才基于诺言和异步/等待。
调用堆栈
调用堆栈是一个LIFO队列(后进先出)。
事件循环不断检查调用堆栈看看是否有任何需要运行的功能。
这样做时,它将找到的所有函数调用添加到调用堆栈中,并按顺序执行每个函数。
您知道在调试器或浏览器控制台中可能熟悉的错误堆栈跟踪吗?浏览器在调用堆栈中查找函数名称,以通知您哪个函数发起了当前调用:
一个简单的事件循环说明
让我们举个例子:
我用
foo
,bar
和baz
作为随机名称。输入任何名称以替换它们。
const bar = () => console.log('bar')
const baz = () => console.log(‘baz’)
const foo = () => {
console.log(‘foo’)
bar()
baz()
}
foo()
此代码打印
foo
bar
baz
如预期的那样。
当此代码运行时,首先foo()
叫做。里面foo()
我们先打电话bar()
,然后我们致电baz()
。
此时,调用堆栈如下所示:
每次迭代中的事件循环都会检查调用堆栈中是否有东西,并执行它:
直到调用堆栈为空。
排队功能执行
上面的示例看起来很正常,没有什么特别的:JavaScript查找要执行的事物,并按顺序运行它们。
让我们看看如何将函数推迟到堆栈清除之前。
用例setTimeout(() => {}), 0)
就是调用一个函数,但是一旦代码中的每个其他函数都执行了,就执行它。
举个例子:
const bar = () => console.log('bar')
const baz = () => console.log(‘baz’)
const foo = () => {
console.log(‘foo’)
setTimeout(bar, 0)
baz()
}
foo()
这段代码打印出来,也许令人惊讶:
foo
baz
bar
运行此代码时,将首先调用foo()。在foo()内部,我们首先调用setTimeout,bar
作为参数,我们指示它尽可能快地运行,并传递0作为计时器。然后我们调用baz()。
此时,调用堆栈如下所示:
这是程序中所有功能的执行顺序:
为什么会这样呢?
消息队列
调用setTimeout()时,Browser或Node.js将启动计时器。计时器到期后,在这种情况下,我们立即将超时值设为0,则将回调函数放入消息队列。
消息队列也是用户启动的事件,例如单击或键盘事件,或者拿来在您的代码有机会对它们进行响应之前,对响应进行排队。或者也DOM像这样的事件onLoad
。
循环将优先级赋予调用堆栈,它首先处理在调用堆栈中找到的所有内容,一旦其中没有任何内容,便开始处理消息队列中的内容。
我们不必等待诸如setTimeout
,抓取或其他方式来完成自己的工作,因为它们是由浏览器提供的,并且它们位于自己的线程中。例如,如果您设置了setTimeout
超时为2秒,您不必等待2秒-等待发生在其他地方。
ES6作业队列
ECMAScript 2015引入了作业队列的概念,Promises使用了该队列(也在ES6 / ES2015中引入)。这是一种尽快执行异步函数结果的方法,而不是放在调用堆栈的末尾。
在当前功能结束之前解决的承诺将在当前功能之后立即执行。
我发现在游乐园中过山车的类比很好:消息队列将您排在队列的后面,在所有其他人的后面,您将不得不等待转弯,而工作队列是快速通道票这样您就可以在完成上一个骑行之后立即骑另一个骑行。
例子:
const bar = () => console.log('bar')
const baz = () => console.log(‘baz’)
const foo = () => {
console.log(‘foo’)
setTimeout(bar, 0)
new Promise((resolve, reject) =>
resolve(‘should be right after baz, before bar’)
).then(resolve => console.log(resolve))
baz()
}
foo()
此打印
foo
baz
should be right after baz, before bar
bar
这是Promises(和基于Promise构建的Async / await)与通过setTimeout()
或其他平台API。
结论
本文向您介绍了Node.js事件循环的基本构建块。
这是用Node.js编写的任何程序的重要组成部分,我希望这里介绍的某些概念将来对您有用。
免费下载我的Node.js手册
更多节点教程:
- npm软件包管理器简介
- Node.js简介
- 使用Axios的HTTP请求
- 在哪里托管Node.js应用
- 使用Node.js与Google Analytics(分析)API进行交互
- npx节点包运行器
- package.json指南
- npm在哪里安装软件包?
- 如何更新Node.js
- 如何使用或执行使用npm安装的软件包
- package-lock.json文件
- 使用npm的语义版本控制
- 您是否应该将node_modules文件夹提交到Git?
- 将所有Node依赖项更新到最新版本
- 使用Node.js解析JSON
- 查找npm软件包的安装版本
- Node.js流
- 安装较旧版本的npm软件包
- 在Node中获取当前文件夹
- 如何在Node中记录对象
- 使用导出从Node文件公开功能
- 节点和浏览器之间的区别
- 使用Node发出HTTP POST请求
- 使用Node获取HTTP请求主体数据
- 节点缓冲区
- Node.js的简要历史
- 如何安装Node.js
- 使用Node您需要知道多少JavaScript?
- 如何使用Node.js REPL
- 节点,从命令行接受参数
- 使用Node输出到命令行
- 接受来自Node中命令行的输入
- 使用`npm uninstall`来卸载npm软件包。
- npm全局或本地软件包
- npm依赖项和devDependencies
- Node.js事件循环
- 了解process.nextTick()
- 了解setImmediate()
- 节点事件发射器
- 建立一个HTTP服务器
- 使用Node发出HTTP请求
- Node fs模块
- 使用Axios的Node中的HTTP请求
- 使用Node读取文件
- 节点文件路径
- 用Node写入文件
- 节点文件统计
- 在Node中使用文件描述符
- 在Node中使用文件夹
- 节点路径模块
- Node http模块
- 将WebSockets与Node.js结合使用
- 使用MySQL和Node的基础知识
- Node.js中的错误处理
- 哈巴狗指南
- 如何从Node.js读取环境变量
- 如何从Node.js程序退出
- Node os模块
- 节点事件模块
- 节点,开发与生产之间的区别
- 如何检查Node.js中是否存在文件
- 如何在Node.js中创建一个空文件
- 如何使用Node.js删除文件
- 如何使用Node.js获取文件的最后更新日期
- 如何在JavaScript中确定日期是否为今天
- 如何将JSON对象写入Node.js中的文件
- 为什么要在下一个项目中使用Node.js?
- 从任何文件夹运行Web服务器
- 如何将MongoDB与Node.js结合使用
- 使用Chrome DevTools调试Node.js应用
- 什么是pnpm?
- Node.js运行时v8选项列表
- 使用npm时如何解决“缺少写访问权限”错误
- 如何在Node.js中启用ES模块
- 如何使用Node.js生成子进程
- 如何在Express中同时获取已解析的正文和原始正文
- 如何在Node.js中处理文件上传
- 节点模块中的对等依赖性是什么?
- 如何使用Node.js编写CSV文件
- 如何使用Node.js读取CSV文件
- 节点核心模块
- 使用Node.js一次增加多个文件夹的数量
- 如何将画布打印到数据URL
- 如何使用Node.js和Canvas创建和保存图像
- 如何使用Node.js下载图像
- 如何在Node.js中批量重命名文件
- 如何获取Node中文件夹中所有文件的名称
- 如何使用Promise和基于Node.js回调的等待功能
- 如何在本地测试NPM软件包
- 如何在运行时检查当前的Node.js版本
- 如何使用Sequelize与PostgreSQL交互
- 使用Node.js服务HTML页面
- 如何解决Node.js中的util.pump不是函数错误