【问题标题】:Are All Node "callback" Functions Potentially Asynchronous?所有节点“回调”函数都可能是异步的吗?
【发布时间】:2013-08-09 05:04:23
【问题描述】:

我是一个进入系统的(相对)节点新手,社区里所有的热情都是“只写回调,一切都是异步的和事件驱动的,别担心!”让我对单个程序中的控制流感到有些困惑(或者用更节点的术语,在更大的程序中处理单个请求期间的控制流)

如果我在节点下运行以下程序

var foo = function(){
    console.log("Called Foo");
};

var bar = function(){
    console.log("Called Bar");
};

var doTheThing = function(arg1, callback){
    callback();
};

doTheThing(true, function() {
    foo();
});
bar();

foo 是否有任何机会 bar 之后执行?当我在本地通过命令行运行程序时,它总是

Called Foo
Called Bar

但我看到很多善意的布道者发出的警告,类似于不要假设你的回调会在你认为它会被调用时被调用,我不清楚他们是否只是警告关于库实现细节,或者当你使用函数对象作为参数时,node.js 是否做了一些奇怪/特殊的事情。

【问题讨论】:

  • 没有机会。你所拥有的一切都是同步的。当您使用异步回调(如 setTimeout 竞争条件),或者当您使用的库使用异步事物(如 jQuery $.Deferred 对象)时,问题就出现了。 编辑:我意识到你主要是在谈论节点,但我不明白为什么会有任何区别......我希望我没有错!但是,除非您特别地(您的代码不是)使用异步功能,否则不应该有顺序会有所不同的原因
  • @Ian 我很确定你是对的,但我做这件事的时间已经够长了,除非我已经测试了 h-l 并得到了,否则我从不假设任何关于新技术的事情来自同行的确认。
  • 嘿,这就是我评论的原因,希望更多的人加入讨论:)

标签: javascript node.js callback


【解决方案1】:

不,没有机会。不适用于该代码。

如果您正在编写自己的函数,或者如果您有权访问代码,则无需假设您知道一切是否同步,但如果您无权访问代码,或者还没有读过它,那么不,你不能假设回调将是同步的。

但是不好的做法做出这样的假设有两个原因,首先是因为它现在是同步的并不意味着其他人,或者忘记未来你以后不能改变它,并且其次,因为如果它都是同步的,为什么你/他们首先使用回调?回调的全部意义在于允许异步调用的可能性。使用回调然后表现得好像它们总是同步的,即使你知道是这种情况,也会让你的代码对其他进入的人感到困惑。

【讨论】:

  • 回复:如果都是同步的,为什么你/他们首先要使用回调——传递匿名函数在编程的某些角落有着悠久而神圣的历史世界,无论代码是同步执行还是异步执行。
  • 是的。像 php 的 usort 或主干的排序函数之类的东西采用函数并且是同步的,需要区别对待。我想我不会在心理上将它们称为“回调”,因为它们遇到的问题是不同的。但是,您仍然需要非常小心范围,并且不要对回调何时被调用做出假设。
【解决方案2】:

没有

您的示例代码是 100% 同步、单线程、简单的自上而下的。但那是因为你没有做任何 I/O,没有任何真正的异步调用,也没有使用 process.nextTicksetTimeoutsetInterval。要更真实地模拟异步调用,请执行以下操作:

function fakeAsync(name, callback) {
  setTimeout(function () {
    callback(null, name);
  }, Math.random() * 5000);
}

function logIt(error, result) {
  console.log(result);
}

fakeAsync('one', logIt);
fakeAsync('two', logIt);
fakeAsync('three', logIt);

运行几次,您有时会看到乱序的结果。

【讨论】:

  • “有时”?大约 5/6 的时间:-)
【解决方案3】:

foo 是否有可能在 bar 之后执行?

在您当前的代码中,没有。尽管您的 doTheThing 函数具有异步函数签名(即,它以回调作为最后一个参数,对于不了解函数实现的局外人来说,它会暗示它是异步的),但它实际上是完全同步的,callback 将被调用而不屈服于运行时。

然而

你真的没有理由给你的doTheThing 代码一个异步签名,除非你愿意在某个时候将真正的异步行为引入doTheThing。到那时,你就有问题了,因为foobar 的调用顺序会颠倒。

在我看来,只有两种像你这样编写代码的好方法:要么让doTheThing 同步(最重要的是:它不依赖于 I/O),要么这意味着您可以简单地从函数中返回:

doTheThing = function(arg1){
   return null
};
doTheThing()
foo()
bar()

或直接更改doTheThing 的存根实现以包含对setImmediate 的调用,即

var doTheThing = function(arg1, callback){
   setImmediate(function() { callback(); );
};

注意,这也可以写成

var doTheThing = function(arg1, callback){
   setImmediate(callback);
};

但这只是因为此时回调不接受任何参数。第一个版本更接近您的版本。

一旦您这样做了,bar 将始终在 foo 之前被调用,现在可以安全地将异步功能引入 doTheThing

【讨论】:

  • Re: “异步函数签名”——一个名为 callback 的参数表示异步行为是否是 node.js 约定?
  • 我不确定它本身是否可以被视为一种约定。只是通常情况下,当库作者公开“同步”功能(即仅进行数字运算的功能)时,通常会使用同步签名公开,因为这样更易于使用。常识,真的。让我们保持这一点,期望回调被异步调用,如果特定库中的函数没有,我会感到很困惑,而且我不得不以某种方式找出困难的方法。我也会提交一份关于它的错误报告。 IMO,好的异步代码存根使用setImmediate 或类似的。
  • 我不知道 Node 核心中有任何违反此规则的函数。 Node 核心中的“同步”函数都有一个同步函数签名(即它们只返回结果)。从这个意义上说,我认为 Node 核心为正确的行为设定了一些期望。
  • 一些不错的阅读:Honour thy async signature。 “在节点中,如果你的函数有一个异步签名,你应该始终确保它是异步的,你应该始终尊重调用者可能假设的异步期望总是隐含的。”
  • @AlanStorm 平。 (不知道你是否会看到这个)
猜你喜欢
  • 2014-03-20
  • 2021-10-29
  • 2016-01-16
  • 2015-09-08
  • 1970-01-01
  • 2018-04-11
  • 1970-01-01
  • 2019-11-05
  • 2013-10-05
相关资源
最近更新 更多