1

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

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

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 是否会被调用。当您使用函数对象作为参数时,js 会做一些奇怪/特殊的事情。

4

3 回答 3

5

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

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

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

于 2013-08-09T05:24:29.397 回答
2

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

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);

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

于 2013-08-09T05:24:13.820 回答
2

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.

于 2013-08-09T08:32:11.863 回答