我知道回调函数是异步运行的,但为什么呢?

IT技术 javascript node.js asynchronous callback
2021-02-02 13:34:29

语法的哪一部分提供了该函数应该在其他线程中运行并且是非阻塞的信息?

让我们考虑 node.js 中的简单异步 I/O

 var fs = require('fs');
 var path = process.argv[2];

  fs.readFile(path, 'utf8', function(err,data) {
   var lines = data.split('\n');
   console.log(lines.length-1);
  });

究竟是什么让它发生在后台?任何人都可以准确地解释它或粘贴一些好的资源的链接吗?我所看到的每个地方都有大量关于回调是什么的信息,但没有人解释为什么它实际上是这样工作的。

这不是关于 node.js 的具体问题,而是关于每种编程语言中回调的一般概念。

编辑:

可能我提供的例子在这里不是最好的。所以我们不要考虑这个 node.js 代码片段。我一般在问 - 是什么让程序在遇到回调函数时继续执行的技巧。使回调概念成为非阻塞概念的语法是什么?

提前致谢!

3个回答

没有在告诉你,你的回调是异步执行的语法。回调可以是异步的,例如:

setTimeout(function(){
    console.log("this is async");
}, 100);

或者它可以是同步的,例如:

an_array.forEach(function(x){
    console.log("this is sync");
});

那么,如何知道函数是同步调用还是异步调用呢?唯一可靠的方法是阅读文档。

您还可以编写一个测试来确定文档是否可用:

var t = "this is async";
some_function(function(){
    t = "this is sync";
});

console.log(t);

异步代码如何工作

Javascript 本身没有任何使函数异步的功能。如果你想写一个异步函数,你有两个选择:

  1. 使用另一个异步函数,例如setTimeout或 web worker 来执行您的逻辑。

  2. 用C写出来。

至于 C 编码的函数(如setTimeout是如何实现异步执行的?这一切都与事件循环(或主要)有关。

事件循环

在 Web 浏览器中,有一段用于联网的代码。最初,网络代码只能下载一件事:HTML 页面本身。当 Mosaic 发明<img>标签时,网络代码演变为下载多个资源。然后 Netscape 实现了图像的渐进式渲染,他们不得不使网络代码异步,以便他们可以在加载所有图像之前绘制页面,并逐步且单独地更新每个图像。这就是事件循环的起源。

在浏览器的核心有一个从异步网络代码演变而来的事件循环。因此,它使用 I/O 原语作为其核心并不奇怪:(select()或类似的东西,例如 poll、epoll 等,取决于操作系统)。

select()C 中函数允许您在单个线程中等待多个 I/O 操作,而无需产生额外的线程。select()看起来像:

select (max, readlist, writelist, errlist, timeout)

要让它等待 I/O(来自套接字或磁盘),您需要将文件描述符添加到 ,readlist当您的任何 I/O 通道上有可用数据时,它将返回。一旦它返回,您就可以继续处理数据。

javascript 解释器保存您的回调,然后调用该select()函数。select()返回时,解释器找出哪个回调与哪个 I/O 通道相关联,然后调用它。

方便的是,select()还允许您指定一个timeout值。通过仔细管理timeout传递给select()您的信息,可以在将来的某个时间调用回调。这就是实施方式setTimeoutsetInterval实施方式。解释器保留所有超时的列表并计算它需要传递timeout给 的内容select()然后当select()返回时除了找出是否有任何由于 I/O 操作需要调用的回调之外,解释器还会检查任何需要调用的过期超时。

所以select()单独涵盖了实现异步功能所需的几乎所有功能。但是现代浏览器也有网络工作者。对于 Web Worker,浏览器会生成线程以异步执行 javascript 代码。为了与主线程通信,worker 仍然必须与事件循环(select()函数)交互

Node.js 在处理文件/磁盘 I/O 时也会产生线程。当 I/O 操作完成时,它会与主事件循环进行通信以导致执行适当的回调。


希望这能回答你的问题。我一直想写这个答案,但之前很忙。如果您想了解更多关于 CI 中的非阻塞 I/O 编程,建议您阅读以下内容:http : //www.gnu.org/software/libc/manual/html_node/Waiting-for-I_002fO.html

有关更多信息,另请参阅:

@szeb libuv 在内部将在编译时选择适当的异步 I/O 库,因此它仍然是select()或类似的东西,例如 poll、epoll 等,取决于操作系统,因此没有更新。当我写这个答案时,Node 只使用了 libevent,但这根本不会改变这个答案,因为 libevent 和现在 libuv 仍然 100% 做这个答案所解释的。这是永远正确的答案
2021-03-16 13:34:29
请注意,node.js 使用 libev 作为其事件处理库。libev 将在编译时根据操作系统选择(无双关语)适当的方法(选择、轮询、epoll、重叠 I/O)。
2021-03-17 13:34:29
@szeb 如果您想更深入地解释操作系统如何提供libuv 使用的诸如select, poll, epoll, Overlapped I/O (Windows) 等服务,请查看我对这个相关问题的低级回答:stackoverflow.com/questions/61262054 /…
2021-03-24 13:34:29
@slebetman 我的评论主要是关于你的第一条评论,我想强调读者如果有兴趣应该谷歌 libuv。无论如何,感谢您的回答和您提供的附加链接
2021-04-07 13:34:29
这是一个绝妙的答案。
2021-04-10 13:34:29

首先,如果某些东西不是 Async,则意味着它正在阻塞。所以 javascript runner 会在该行停止,直到该函数结束(这就是 readFileSync 会做的事情)。

众所周知,fs 是一个 IO 库,因此这种事情需要时间(告诉硬件读取某些文件不是立即完成的事情),因此任何不需要 CPU 的事情都很有意义,它是异步的,因为它需要时间,并且不需要冻结其余代码以等待另一块硬件(当 CPU 空闲时)。

我希望这能解决你的疑惑。

@DCDC JavaScript 语法中没有任何内容对此进行定义。
2021-03-13 13:34:29
谢谢,但我在问别的事情。语法的哪一部分,通常 - 也许 node.js 是这里的坏例子,告诉“在后台做”
2021-03-21 13:34:29
哦,我以为你问为什么。回调参数是因为 API 就是这样,并且因为我刚刚说过的内容而有意义。如果你想了解异步是如何实现的,你可以阅读多线程,或者 javascript 引擎如何通过实现事件循环来支持异步代码developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop但如果你想真正进入这个问题,它将是 C++ 代码,而不是 JS
2021-03-25 13:34:29
你能用 C++ 解释一下吗?谢谢!
2021-03-27 13:34:29

回调不一定是异步的。执行完全取决于fs.readFile决定如何处理函数参数。

在 JavaScript 中,您可以使用例如setTimeout异步执行函数

讨论和资源:

node.js 如何实现非阻塞 I/O?

并发模型和事件循环

维基百科

有两种类型的回调,它们在运行时控制数据流的方式不同:阻塞回调(也称为同步回调或仅回调)和延迟回调(也称为异步回调)。

好的,所以也许这是一个不好的例子,但通常传递的代码块是异步运行的,不是吗?
2021-03-14 13:34:29
传递函数什么都不做。某些东西必须执行该功能,并且可以以开发人员想要的任何方式完成。示例:jsfiddle.net/pfudnyh6/3
2021-03-21 13:34:29
@DCDC 没有办法概括这一点。回调在被调用时运行,这完全取决于您将回调传递给什么。
2021-03-31 13:34:29