我刚开始使用node.js.我做了一些ajax的东西,但没有太复杂,所以回调仍然是我的头脑。我看了异步,但我只需要按顺序运行一些函数。
我基本上有一些东西从API中提取一些JSON,创建一个新的,然后用它做一些事情。显然,我不能只运行它,因为它一次运行所有内容并且有一个空的JSON。大多数进程必须按顺序运行,但是如果从API中提取JSON,它可以在等待时拉出其他JSON然后就可以了。我把回调放在一个循环中时感到很困惑。我该怎么处理索引?我想我已经看到一些在循环中使用回调的地方作为一种递归函数,并且根本不使用for循环。
简单的例子会有很多帮助。
答案 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使用更新的术语),但概念没有改变。