用node.js理解javascript回调的概念,特别是在循环中

时间:2010-12-22 04:49:00

标签: javascript callback node.js

我刚开始使用node.js.我做了一些ajax的东西,但没有太复杂,所以回调仍然是我的头脑。我看了异步,但我只需要按顺序运行一些函数。

我基本上有一些东西从API中提取一些JSON,创建一个新的,然后用它做一些事情。显然,我不能只运行它,因为它一次运行所有内容并且有一个空的JSON。大多数进程必须按顺序运行,但是如果从API中提取JSON,它可以在等待时拉出其他JSON然后就可以了。我把回调放在一个循环中时感到很困惑。我该怎么处理索引?我想我已经看到一些在循环中使用回调的地方作为一种递归函数,并且根本不使用for循环。

简单的例子会有很多帮助。

1 个答案:

答案 0 :(得分:86)

如果回调是在相同的范围内定义的,则定义循环(通常是这种情况),然后回调将有权访问索引变量。暂且不谈NodeJS细节,让我们考虑一下这个功能:

function doSomething(callback) {
    callback();
}

该函数接受回调函数引用,它所做的就是调用它。不是很令人兴奋。 : - )

现在让我们在循环中使用它:

var index;

for (index = 0; index < 3; ++index) {
    doSomething(function() {
        console.log("index = " + index);
    });
}

(在计算密集型代码中 - 就像服务器进程一样 - 最好不要在生产代码中完成上述操作,我们马上回过头来看。)

现在,当我们运行时,我们会看到预期的输出:

index = 0
index = 1
index = 2

我们的回调能够访问index,因为回调是对其定义范围内的数据的闭包。 (不要担心术语“封闭”,closures are not complicated。)

我之所以说最好不要在计算密集型生产代码中执行上述操作,原因是代码在每次迭代上创建一个函数(除非在编译器中进行花哨的优化,而V8是非常聪明,但优化创建这些功能是非常重要的。所以这是一个稍微重做的例子:

var index;

for (index = 0; index < 3; ++index) {
    doSomething(doSomethingCallback);
}

function doSomethingCallback() {
    console.log("index = " + index);
}

这可能看起来有点令人惊讶,但它仍然以相同的方式运行,并且仍然具有相同的输出,因为doSomethingCallback仍然是index上的闭包,因此它仍然看到{的值{1}}当它被调用时。但现在只有一个index函数,而不是每个循环中的新函数。

现在让我们采取一个反面的例子,的工作:

doSomethingCallback

失败,因为foo(); function foo() { var index; for (index = 0; index < 3; ++index) { doSomething(myCallback); } } function myCallback() { console.log("index = " + index); // <== Error } 未定义在myCallback定义的同一范围(或嵌套范围)中,因此index未在index内定义}。

最后,让我们考虑在循环中设置事件处理程序,因为必须要小心。在这里,我们将深入探讨NodeJS:

myCallback

似乎就像上面应该像我们之前的循环那样工作,但是有一个至关重要的区别。在我们之前的循环中,回调被立即调用,因此它看到了正确的var spawn = require('child_process').spawn; var commands = [ {cmd: 'ls', args: ['-lh', '/etc' ]}, {cmd: 'ls', args: ['-lh', '/usr' ]}, {cmd: 'ls', args: ['-lh', '/home']} ]; var index, command, child; for (index = 0; index < commands.length; ++index) { command = commands[index]; child = spawn(command.cmd, command.args); child.on('exit', function() { console.log("Process index " + index + " exited"); // <== WRONG }); } 值,因为index还没有机会继续前进。但是,在上面,我们将在调用回调之前旋转循环。结果?我们看到了

index

这是至关重要的一点。闭包没有关闭的数据的副本,它有一个实时引用。因此,当每个进程的Process index 3 exited Process index 3 exited Process index 3 exited 回调运行时,循环将已经完成,因此所有三个调用都看到相同的exit值(其值为 end < / em>的循环)。

我们可以通过让回调使用不会改变的不同的变量来解决这个问题,如下所示:

index

现在我们输出正确的值(按进程退出的顺序):

var spawn = require('child_process').spawn;

var commands = [
    {cmd: 'ls', args: ['-lh', '/etc' ]},
    {cmd: 'ls', args: ['-lh', '/usr' ]},
    {cmd: 'ls', args: ['-lh', '/home']}
];
var index, command, child;

for (index = 0; index < commands.length; ++index) {
    command = commands[index];
    child = spawn(command.cmd, command.args);
    child.on('exit', makeExitCallback(index));
}

function makeExitCallback(i) {
    return function() {
        console.log("Process index " + i + " exited");
    };
}

有效的方式是我们分配给Process index 1 exited Process index 2 exited Process index 0 exited 事件的回调将关闭我们对exit的调用中的i参数。 makeExitCallback创建并返回的第一个回调将关闭该makeExitCallback调用的i值,其创建的第二个回调将关闭makeExitCallback值。 调用i(与之前调用的makeExitCallback值不同)等。

如果你给the article linked above一个阅读,那么一些事情应该更清楚。文章中的术语有点过时(ECMAScript 5使用更新的术语),但概念没有改变。