通过node.js理解javascriptcallback的概念,特别是在循环中

我刚刚开始与node.js。 我已经做了一些Ajax的东西,但没有太复杂,所以callback仍然是我的头。 我看着asynchronous,但我需要的是顺序运行一些function。

我基本上有一些东西,从一个API拉一些JSON,创build一个新的,然后做了什么。 显然,我不能只运行它,因为它一次运行一切,并有一个空的JSON。 大多数进程必须按顺序运行,但是如果从API中拉取JSON,它可以在等待的时候拉取其他JSON,那么这很好。 把callback放在循环中时我感到困惑。 我该如何处理索引? 我想我已经看到一些在循环内部使用callback的地方作为一种recursion函数,并且根本不使用for循环。

简单的例子会帮助很多。

如果callback是在同一个范围内定义的,则循环定义在(通常是这种情况),那么callback将有权访问索引variables。 暂且抛开NodeJS的细节,让我们考虑这个function:

function doSomething(callback) { callback(); } 

该函数接受一个callback函数引用,它所做的就是调用它。 不是很令人兴奋。 🙂

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

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

(在计算密集型代码中 – 就像服务器进程一样 – 最好不要在生产代码中按字面做上述操作,我们稍后会回到这一点。)

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

 index = 0 index = 1 index = 2 

我们的callback函数能够访问index ,因为callback函数是定义范围内的数据的一个闭包 。 (不要担心术语“closures”, closures并不复杂 。)

我之所以说在计算密集型生产代码中最好不要这样做,是因为代码在每一次迭代中都创build了一个函数(除了在编译器中进行优化之外,V8是非常聪明的,但是优化创build这些函数是非平凡)。 所以这是一个稍微修改过的例子:

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

这可能看起来有点令人惊讶,但它仍然以相同的方式工作,并且仍然具有相同的输出,因为doSomethingCallback仍然是一个关于index的闭包,所以它仍然可以看到index的值。 但是现在只有一个doSomethingCallback函数,而不是每个循环中的新鲜函数。

现在我们来看一个负面的例子,这是行不通的:

 foo(); function foo() { var index; for (index = 0; index < 3; ++index) { doSomething(myCallback); } } function myCallback() { console.log("index = " + index); // <== Error } 

这会失败,因为myCallback没有在相同的范围(或嵌套的范围)中定义该index是在中定义的,所以indexmyCallback是未定义的。

最后,让我们考虑在循环中设置事件处理程序,因为必须小心。 这里我们将深入到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 }); } 

看起来上面应该和我们以前的循环一样工作,但是有一个关键的区别。 在我们之前的循环中,callback被立即调用,所以它看到了正确的index值,因为index还没有机会继续前进。 但是,在上面,我们将在调用callback之前旋转循环。 结果? 我们看

 Process index 3 exited Process index 3 exited Process index 3 exited 

这是一个关键点。 闭包没有closures的数据副本 ,它有一个实时引用 。 所以,当每个进程的exitcallback被运行的时候,循环已经完成,所有三个调用都会看到相同的index值(它是循环结尾的值)。

我们可以通过callback使用一个不会改变的variables来解决这个问题,就像这样:

 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事件的callback在我们对makeExitCallback的调用中closures了i参数。 makeExitCallback创build和返回的第一个callbackclosures了对该makeExitCallback调用的i值,它创build的第二个callbackclosures了对该makeExitCallback调用的i值(这与先前调用的i值不同),等等。

如果你把上面的文章连上一篇文章 ,应该更清楚一些东西。 文章中的术语有点过时(ECMAScript 5使用更新的术语),但概念没有改变。