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

Mr *_*SON 31 javascript callback node.js

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

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

简单的例子会有很大帮助.

T.J*_*der 86

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

function doSomething(callback) {
    callback();
}
Run Code Online (Sandbox Code Playgroud)

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

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

var index;

for (index = 0; index < 3; ++index) {
    doSomething(function() {
        console.log("index = " + index);
    });
}
Run Code Online (Sandbox Code Playgroud)

(在计算密集型代码中 - 就像服务器进程一样 - 最好不要在生产代码中按字面意思执行上述操作,我们马上回过头来看.)

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

index = 0
index = 1
index = 2
Run Code Online (Sandbox Code Playgroud)

我们的回调能够访问index,因为回调是对其定义范围内的数据的闭包.(不要担心术语"关闭",关闭并不复杂.)

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

var index;

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

function doSomethingCallback() {
    console.log("index = " + index);
}
Run Code Online (Sandbox Code Playgroud)

这可能看起来有点令人惊讶,但它仍然以相同的方式工作,并且仍然具有相同的输出,因为doSomethingCallback它仍然是一个闭包index,所以它仍然可以看到index它被调用时的值.但现在只有一个doSomethingCallback功能,而不是每个循环上的新功能.

现在让我们采取一个反面的例子,这是不起作用的:

foo();

function foo() {
    var index;

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

function myCallback() {
    console.log("index = " + index); // <== Error
}
Run Code Online (Sandbox Code Playgroud)

这失败了,因为myCallback没有在定义的同一范围(或嵌套范围)index中定义,因此index未在其中定义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
    });
}
Run Code Online (Sandbox Code Playgroud)

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

Process index 3 exited
Process index 3 exited
Process index 3 exited
Run Code Online (Sandbox Code Playgroud)

这是至关重要的一点.闭包没有它关闭的数据的副本,它有一个实时引用.因此,当exit每个进程的回调运行时,循环将已经完成,因此所有三个调用都看到相同的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");
    };
}
Run Code Online (Sandbox Code Playgroud)

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

Process index 1 exited
Process index 2 exited
Process index 0 exited
Run Code Online (Sandbox Code Playgroud)

有效的方法是我们分配给exit事件的回调将关闭i我们调用的调用中的参数makeExitCallback.makeExitCallback创建和返回的第一个回调将关闭i该调用的值,makeExitCallback它创建的第二个回调将关闭调用的i值(不同于之前调用的值),等等.makeExitCallbacki

如果您将上面链接的文章作为阅读,那么应该更清楚一些事情.文章中的术语有点过时(ECMAScript 5使用更新的术语),但概念没有改变.

  • 令人难以置信的答案,以及你提到的优秀文章,它对我帮助很大 (6认同)