我只是从node.js开始。我做了一些ajax的工作,但是没有什么太复杂的,所以回调仍然有点麻烦。我看了看异步,但我所需要的只是顺序运行一些功能。
我基本上有一些东西可以从API中提取一些JSON,然后创建一个新的JSON,然后对此进行处理。显然,我不能只运行它,因为它可以一次运行所有内容并且具有空的JSON。通常,流程必须按顺序运行,但是如果在从API中提取JSON时可以在等待时提取其他JSON,那很好。将回调置于循环中时,我只是感到困惑。我该如何处理索引?我想我已经看到一些地方在循环中使用回调作为递归函数,而根本不使用循环。
简单的例子会很有帮助。
如果在与循环定义相同的作用域中定义了回调(通常是这种情况),则回调将有权访问index变量。暂时搁置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,因为回调是对定义了作用域的数据的 封闭 。(不要担心术语“ closure”,闭包并不复杂。)
index
我说过最好不要在计算密集型生产代码中执行上述操作,原因是该代码会在 每次迭代 时 都 创建一个函数(除非在编译器中进行了花哨的优化,而V8非常聪明,但通过创建这些函数来进行优化是可以的)不平凡的)。因此,这是一个稍作修改的示例:
var index; for (index = 0; index < 3; ++index) { doSomething(doSomethingCallback); } function doSomethingCallback() { console.log("index = " + index); }
这可能看起来有点令人惊讶,但是它仍然以相同的方式工作,并且仍然具有相同的输出,因为doSomethingCallback它仍然是over的闭包index,因此index在调用时仍能看到as 的值。但是现在只有一个doSomethingCallback函数,而不是每个循环都有一个新函数。
doSomethingCallback
现在让我们举一个负面的例子,那 是 行不通的:
foo(); function foo() { var index; for (index = 0; index < 3; ++index) { doSomething(myCallback); } } function myCallback() { console.log("index = " + index); // <== Error }
之所以失败,是因为myCallback未在中定义相同的范围(或嵌套范围)中index进行定义,因此index在中未定义myCallback。
myCallback
最后,让我们考虑在一个循环中设置事件处理程序,因为对此必须要小心。在这里,我们将深入探讨NodeJS:
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运行这些进程中的每个进程的回调时,循环将已经完成,因此所有三个调用都将看到相同的index值(其值在循环 结束 时)。
exit
我们可以通过使回调使用不会改变的 其他 变量来解决此问题,如下所示:
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(这是比不同i的先前调用值)等。
i
makeExitCallback
如果您对上面链接的文章进行了阅读,那么很多事情都应该更加清楚。本文中的术语有些过时(ECMAScript 5使用更新的术语),但是概念没有改变。