在 Node.js 中,所有异步操作都在 Node.js Javascript 单线程之外完成其任务。它们要么使用本机代码线程(例如node.js中的磁盘I/O),要么根本不使用线程(例如事件驱动的网络或计时器)。
您无法将完全用 Node.js Javascript 编写的同步操作神奇地使其异步。异步操作是异步的,因为它调用一些在本机代码中实现并以实际异步方式编写的函数。因此,要使某些内容异步,必须专门编写它以使用本身与异步本机代码实现异步的较低级别操作。
这些带外操作然后通过事件队列与主 Node.js Javascript 线程进行通信。当其中一个异步操作完成时,它会向 Javascript 事件队列添加一个事件,然后当单个 Node.js 线程完成当前正在执行的操作时,它会从事件队列中获取下一个事件并调用与该事件关联的回调。
因此,您可以并行运行多个异步操作。并行运行 3 个操作通常比顺序运行这 3 个操作的端到端运行时间更短。
让我们检查一下现实世界的异步情况而不是伪代码:
function doSomething() {
fs.readFile(fname, function(err, data) {
console.log("file read");
});
setTimeout(function() {
console.log("timer fired");
}, 100);
http.get(someUrl, function(err, response, body) {
console.log("http get finished");
});
console.log("READY");
}
doSomething();
console.log("AFTER");
以下是逐步发生的情况:
-
fs.readFile()
已启动。由于node.js使用线程池实现文件I/O,因此该操作被传递给node.js中的一个线程,并且它将在一个单独的线程中运行。
- 无需等待
fs.readFile()
完成,setTimeout()
叫做。这使用了 libuv(node.js 构建的跨平台库)中的计时器子系统。这也是非阻塞的,因此计时器被注册,然后继续执行。
-
http.get()
叫做。这将发送所需的 http 请求,然后立即返回以进一步执行。
-
console.log("READY")
会跑。
- 这三个异步操作将以不确定的顺序完成(无论哪个先完成其操作,都会先完成)。出于本次讨论的目的,我们假设
setTimeout()
首先完成。当它完成时,node.js 中的一些内部组件将在事件队列中插入一个事件,其中包含计时器事件和注册的回调。当 Node.js 主 JS 线程执行完任何其他 JS 后,它将从事件队列中获取下一个事件并调用与其关联的回调。
- 出于本描述的目的,假设在执行计时器回调时,
fs.readFile()
操作完成。使用它自己的线程,它将在 node.js 事件队列中插入一个事件。
- Now the
setTimeout()
回调完成。此时,JS 解释器会检查事件队列中是否还有其他事件。这fs.readfile()
事件在队列中,因此它会获取该事件并调用与之关联的回调。该回调执行并完成。
- 一段时间后,
http.get()
操作完成。在 Node.js 内部,事件被添加到事件队列中。由于事件队列中没有其他内容,并且 JS 解释器当前未执行,因此可以立即处理该事件并回调http.get()
可以被呼叫。
根据上述事件序列,您将在控制台中看到以下内容:
READY
AFTER
timer fired
file read
http get finished
请记住,这里最后三行的顺序是不确定的(它只是基于不可预测的执行速度),因此这里的精确顺序只是一个示例。如果您需要以特定顺序执行这些操作,或者需要知道这三个操作何时完成,那么您必须添加额外的代码来跟踪它们。
由于您似乎正在尝试通过使当前不是异步的异步操作来使代码运行得更快,所以让我重复一遍。您不能采用完全用 JavaScript 编写的同步操作并“使其异步”。您必须从头开始重写它以使用根本不同的异步较低级别操作,或者您必须将其传递给其他进程来执行,然后在完成时收到通知(使用工作进程或外部进程或本机代码)插件或类似的东西)。