【问题标题】:I know that callback function runs asynchronously, but why?我知道回调函数是异步运行的,但是为什么呢?
【发布时间】:2015-07-05 04:26:25
【问题描述】:

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

让我们考虑 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代码sn-p。我一般问 - 是什么让程序在遇到回调函数时继续执行的技巧。语法是什么 这使得回调概念成为非阻塞概念?

提前致谢!

【问题讨论】:

  • 你已经有了答案,但你也可以阅读javascript callback : functions are objects
  • 无法一概而论。回调在被调用时运行,这完全取决于您将回调传递给什么,与语言无关。
  • 您发布的代码中没有任何内容与异步处理有关。事实上,JavaScript 不是一种多线程语言,因此您永远无法真正“在后台触发某些事情”。但是,您可以通过使用诸如 Q 之类的第三方库来模拟异步行为,这些库可以轻松创建承诺链。一些核心节点函数可能在不同的线程上运行,但您无权访问它们,也无法控制它们的行为。
  • JavaScript 的语法中没有任何内容定义同步与异步。
  • 也许问这个问题的另一种方式是......如果我想编写一个简单的 JavaScript 函数,它接受另一个函数作为参数并在不阻塞调用者的情况下调用该函数,我将如何编写它?

标签: javascript node.js asynchronous callback


【解决方案1】:

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

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 workers 来执行你的逻辑。

  2. 用 C 编写。

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

事件循环

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

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

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

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

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

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

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

所以select() 几乎涵盖了实现异步功能所需的所有功能。但是现代浏览器也有网络工作者。在 web worker 的情况下,浏览器会产生线程来异步执行 javascript 代码。要与主线程通信,worker 仍必须与事件循环(select() 函数)交互。

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


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

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

【讨论】:

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

首先,如果某些东西不是异步的,则意味着它正在阻塞。因此,javascript 运行程序会在该行停止,直到该函数结束(这就是 readFileSync 会做的事情)。

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

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

【讨论】:

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

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

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

讨论和资源:

How does node.js implement non-blocking I/O?

Concurrency model and Event Loop

Wikipedia:

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

【讨论】:

  • 好的,所以也许这是一个不好的例子,但通常传递代码块是异步运行的,不是吗?
  • @DCDC 无法一概而论。回调在被调用时运行,这完全取决于您将回调传递给什么。
  • 传递一个函数什么都不做。某些东西必须执行该功能,并且可以按照开发人员想要的任何方式完成。示例:jsfiddle.net/pfudnyh6/3
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-01
  • 1970-01-01
  • 2016-11-25
  • 2021-09-05
  • 1970-01-01
相关资源
最近更新 更多