队列是 Node.js 中用于有效处理异步操作的一项重要技术。【视频教程推荐:node js教程 】
在本文中,我们将深入研究 Node.js 中的队列:它们是什么,它们如何工作(通过事件循环)以及它们的类型。
Node.js 中的队列是什么?
队列是 Node.js 中用于组织异步操作的数据结构。这些操作以不同的形式存在,包括HTTP请求、读取或写入文件操作、流等。
在 Node.js 中处理异步操作非常具有挑战性。
HTTP 请求期间可能会出现不可预测的延迟(或者更糟糕的可能性是没有结果),具体取决于网络质量。尝试用 Node.js 读写文件时也有可能会产生延迟,具体取决于文件的大小。
类似于计时器和其他的许多操作,异步操作完成的时间也有可能是不确定的。
在这些不同的延迟情况之下,Node.js 需要能够有效地处理所有这些操作。
Node.js 无法处理基于 first-start-first-handle (先开始先处理)或 first-finish-first-handle (先结束先处理)的操作。
之所以不能这样做的一个原因是,在一个异步操作中可能还会包含另一个异步操作。
为第一个异步过程留出空间意味着必须先要完成内部异步过程,然后才能考虑队列中的其他异步操作。
有许多情况需要考虑,因此最好的选择是制定规则。这个规则影响了事件循环和队列在 Node.js 中的工作方式。
让我们简要地看一下 Node.js 是怎样处理异步操作的。
调用栈,事件循环和回调队列
调用栈被用于跟踪当前正在执行的函数以及从何处开始运行。当一个函数将要执行时,它会被添加到调用堆栈中。这有助于 JavaScript 在执行函数后重新跟踪其处理步骤。
回调队列是在后台操作完成时把回调函数保存为异步操作的队列。它们以先进先出(FIFO)的方式工作。我们将会在本文后面介绍不同类型的回调队列。
请注意,Node.js 负责所有异步活动,因为 JavaScript 可以利用其单线程性质来阻止产生新的线程。
在完成后台操作后,它还负责向回调队列添加函数。 JavaScript 本身与回调队列无关。同时事件循环会连续检查调用栈是否为空,以便可以从回调队列中提取一个函数并添加到调用栈中。事件循环仅在执行所有同步操作之后才检查队列。
那么,事件循环是按照什么样的顺序从队列中选择回调函数的呢?
首先,让我们看一下回调队列的五种主要类型。
回调队列的类型
IO 队列(IO queue)
IO操作是指涉及外部设备(如计算机的硬盘、网卡等)的操作。常见的操作包括读写文件操作、网络操作等。这些操作应该是异步的,因为它们留给 Node.js 处理。
JavaScript 无法访问计算机的内部设备。当执行此类操作时,JavaScript 会将其传输到 Node.js 以在后台处理。
完成后,它们将会被转移到 IO 回调队列中,来进行事件循环,以转移到调用栈中执行。
计时器队列(Timer queue)
每个涉及 Node.js 计时器功能的操作(如 setTimeout()
和 setInterval()
)都是要被添加到计时器队列的。
请注意,JavaScript 语言本身没有计时器功能。它使用 Node.js 提供的计时器 API(包括 setTimeout
)执行与时间相关的操作。所以计时器操作是异步的。无论是 2 秒还是 0 秒,JavaScript 都会把与时间相关的操作移交给 Node.js,然后将其完成并添加到计时器队列中。
例如:
setTimeout(function() { console.log('setTimeout'); }, 0) console.log('yeah') # 返回 yeah setTimeout
在处理异步操作时,JavaScript 会继续执行其他操作。只有在所有同步操作都已被处理完毕后,事件循环才会进入回调队列。
微任务队列(Microtask queue)
该队列分为两个队列:
- 第一个队列包含因
process.nextTick
函数而延迟的函数。
事件循环执行的每个迭代称为一个 tick(时间刻度)。
process.nextTick
是一个函数,它在下一个 tick (即事件循环的下一个迭代)执行一个函数。微任务队列需要存储此类函数,以便可以在下一个 tick 执行它们。
这意味着事件循环必须继续检查微任务队列中的此类函数,然后再进入其他队列。
- 第二个队列包含因
promises
而延迟的函数。
如你所见,在 IO 和计时器队列中,所有与异步操作有关的内容都被移交给了异步函数。
但是 promise 不同。在 promise 中,初始变量存储在 JavaScript 内存中(你可能已经注意到了fcfd9c63d2b5ae1697cab5937aad0a2f
)。
异步操作完成后,Node.js 会将函数(附加到 Promise)放在微任务队列中。同时它用得到的结果来更新 JavaScript 内存中的变量,以使该函数不与 fcfd9c63d2b5ae1697cab5937aad0a2f
一起运行。
以下代码说明了 promise 是如何工作的:
let prom = new Promise(function (resolve, reject) { // 延迟执行 setTimeout(function () { return resolve("hello"); }, 2000); }); console.log(prom); // Promise { <pending> } prom.then(function (response) { console.log(response); }); // 在 2000ms 之后,输出 // hello
关于微任务队列,需要注意一个重要功能,事件循环在进入其他队列之前要反复检查并执行微任务队列中的函数。例如,当微任务队列完成时,或者说计时器操作执行了 Promise 操作,事件循环将会在继续进入计时器队列中的其他函数之前参与该 Promise 操作。
因此,微任务队列比其他队列具有最高的优先级。
检查队列(Check queue)
检查队列也称为即时队列(immediate queue)。IO 队列中的所有回调函数均已执行完毕后,立即执行此队列中的回调函数。setImmediate
用于向该队列添加函数。
例如:
const fs = require('fs'); setImmediate(function() { console.log('setImmediate'); }) // 假设此操作需要 1ms fs.readFile('path-to-file', function() { console.log('readFile') }) // 假设此操作需要 3ms do...while...
执行该程序时,Node.js 把 setImmediate
回调函数添加到检查队列。由于整个程序尚未准备完毕,因此事件循环不会检查任何队列。
因为 readFile
操作是异步的,所以会移交给 Node.js,之后程序将会继续执行。
do while
操作持续 3ms。在这段时间内,readFile
操作完成并被推送到 IO 队列。完成此操作后,事件循环将会开始检查队列。
尽管首先填充了检查队列,但只有在 IO 队列为空之后才考虑使用它。所以在 setImmediate
之前,将 readFile
输出到控制台。
关闭队列(Close queue)
此队列存储与关闭事件操作关联的函数。
包括以下内容:
这些队列被认为是优先级最低的,因为此处的操作会在以后发生。
你肯sing不希望在处理 promise 函数之前在 close 事件中执行回调函数。当服务器已经关闭时,promise 函数会做些什么呢?
队列顺序
微任务队列具有最高优先级,其次是计时器队列,I/O队列,检查队列,最后是关闭队列。
回调队列的例子
让我们通过一个更复杂的例子来说明队列的类型和顺序:
const fs = require("fs"); // 假设此操作需要 2ms fs.writeFile('./new-file.json', '...', function() { console.log('writeFile') }) // 假设这需要 10ms 才能完成 fs.readFile("./file.json", function(err, data) { console.log("readFile"); }); // 不需要假设,这实际上需要 1ms setTimeout(function() { console.log("setTimeout"); }, 1000); // 假设此操作需要 3ms while(...) { ... } setImmediate(function() { console.log("setImmediate"); }); // 解决 promise 需要 4 ms let promise = new Promise(function (resolve, reject) { setTimeout(function () { return resolve("promise"); }, 4000); }); promise.then(function(response) { console.log(response) }) console.log("last line");
程序流程如下:
- 在 0 毫秒时,程序开始。
- 在 Node.js 将回调函数添加到 IO 队列之前,
fs.writeFile
在后台花费 2 毫秒。
fs.readFile
takes 10ms at the background before Node.js adds the callback function to the IO queue.
- 在 Node.js 将回调函数添加到 IO 队列之前,
fs.readFile
在后台花费 10 毫秒。 - 在 Node.js 将回调函数添加到计时器队列之前,
setTimeout
在后台花费 1ms。 - 现在,while 操作(同步)需要 3ms。在此期间,线程被阻止(请记住 JavaScript 是单线程的)。
- 同样在这段时间内,
setTimeout
和fs.writeFile
操作完成,并将它们的回调函数分别添加到计时器和 IO 队列中。
现在的队列是:
// queues Timer = [ function () { console.log("setTimeout"); }, ]; IO = [ function () { console.log("writeFile"); }, ];
setImmediate
将回调函数添加到 Check 队列中:
js // 队列 Timer... IO... Check = [ function() {console.log("setImmediate")} ]
在将 promise 操作添加到微任务队列之前,需要花费 4ms 的时间在后台进行解析。
最后一行是同步的,因此将会立即执行:
# 返回 "last line"
因为所有同步活动都已完成,所以事件循环开始检查队列。由于微任务队列为空,因此它从计时器队列开始:
// 队列 Timer = [] // 现在是空的 IO... Check... # 返回 "last line" "setTimeout"
当事件循环继续执行队列中的回调函数时,promise
操作完成并被添加到微任务队列中:
// 队列 Timer = []; Microtask = [ function (response) { console.log(response); }, ]; IO = []; // 当前是空的 Check = []; // 当前是在 IO 的后面,为空 # results "last line" "setTimeout" "writeFile" "setImmediate"
几秒钟后,readFile
操作完成,并添加到 IO 队列中:
// 队列 Timer = []; Microtask = []; // 当前是空的 IO = [ function () { console.log("readFile"); }, ]; Check = []; # results "last line" "setTimeout" "writeFile" "setImmediate" "promise"
最后,执行所有回调函数:
// 队列 Timer = [] Microtask = [] IO = [] // 现在又是空的 Check = []; # results "last line" "setTimeout" "writeFile" "setImmediate" "promise" "readFile"
这里要注意的三点:
- 异步操作取决于添加到队列之前的延迟时间。并不取决于它们在程序中的存放顺序。
- 事件循环在每次迭代之继续检查其他任务之前,会连续检查微任务队列。
- 即使在后台有另一个 IO 操作(
readFile
),事件循环也会执行检查队列中的函数。这样做的原因是此时 IO 队列为空。请记住,在执行 IO 队列中的所有的函数之后,将会立即运行检查队列回调。
总结
JavaScript 是单线程的。每个异步函数都由依赖操作系统内部函数工作的 Node.js 去处理。
Node.js 负责将回调函数(通过 JavaScript 附加到异步操作)添加到回调队列中。事件循环会确定将要在每次迭代中接下来要执行的回调函数。
了解队列如何在 Node.js 中工作,使你对其有了更好的了解,因为队列是环境的核心功能之一。 Node.js 最受欢迎的定义是 non-blocking
(非阻塞),这意味着异步操作可以被正确的处理。都是因为有了事件循环和回调队列才能使此功能生效。
更多编程相关知识,可访问:编程教学!!
以上是深入解析 Node.js 的回调队列的详细内容。更多信息请关注PHP中文网其他相关文章!

Python和JavaScript在开发环境上的选择都很重要。1)Python的开发环境包括PyCharm、JupyterNotebook和Anaconda,适合数据科学和快速原型开发。2)JavaScript的开发环境包括Node.js、VSCode和Webpack,适用于前端和后端开发。根据项目需求选择合适的工具可以提高开发效率和项目成功率。

是的,JavaScript的引擎核心是用C语言编写的。1)C语言提供了高效性能和底层控制,适合JavaScript引擎的开发。2)以V8引擎为例,其核心用C 编写,结合了C的效率和面向对象特性。3)JavaScript引擎的工作原理包括解析、编译和执行,C语言在这些过程中发挥关键作用。

JavaScript是现代网站的核心,因为它增强了网页的交互性和动态性。1)它允许在不刷新页面的情况下改变内容,2)通过DOMAPI操作网页,3)支持复杂的交互效果如动画和拖放,4)优化性能和最佳实践提高用户体验。

C 和JavaScript通过WebAssembly实现互操作性。1)C 代码编译成WebAssembly模块,引入到JavaScript环境中,增强计算能力。2)在游戏开发中,C 处理物理引擎和图形渲染,JavaScript负责游戏逻辑和用户界面。

JavaScript在网站、移动应用、桌面应用和服务器端编程中均有广泛应用。1)在网站开发中,JavaScript与HTML、CSS一起操作DOM,实现动态效果,并支持如jQuery、React等框架。2)通过ReactNative和Ionic,JavaScript用于开发跨平台移动应用。3)Electron框架使JavaScript能构建桌面应用。4)Node.js让JavaScript在服务器端运行,支持高并发请求。

Python更适合数据科学和自动化,JavaScript更适合前端和全栈开发。1.Python在数据科学和机器学习中表现出色,使用NumPy、Pandas等库进行数据处理和建模。2.Python在自动化和脚本编写方面简洁高效。3.JavaScript在前端开发中不可或缺,用于构建动态网页和单页面应用。4.JavaScript通过Node.js在后端开发中发挥作用,支持全栈开发。

C和C 在JavaScript引擎中扮演了至关重要的角色,主要用于实现解释器和JIT编译器。 1)C 用于解析JavaScript源码并生成抽象语法树。 2)C 负责生成和执行字节码。 3)C 实现JIT编译器,在运行时优化和编译热点代码,显着提高JavaScript的执行效率。

JavaScript在现实世界中的应用包括前端和后端开发。1)通过构建TODO列表应用展示前端应用,涉及DOM操作和事件处理。2)通过Node.js和Express构建RESTfulAPI展示后端应用。


热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

适用于 Eclipse 的 SAP NetWeaver 服务器适配器
将Eclipse与SAP NetWeaver应用服务器集成。

ZendStudio 13.5.1 Mac
功能强大的PHP集成开发环境

螳螂BT
Mantis是一个易于部署的基于Web的缺陷跟踪工具,用于帮助产品缺陷跟踪。它需要PHP、MySQL和一个Web服务器。请查看我们的演示和托管服务。

PhpStorm Mac 版本
最新(2018.2.1 )专业的PHP集成开发工具