使用 node.js 理解 javascript 回调的概念,尤其是在循环中
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/4506240/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
understanding the concept of javascript callbacks with node.js, especially in loops
提问by Mr JSON
I am just starting with node.js. I have done a little ajax stuff but nothing too complicated so callbacks are still kind of over my head. I looked at async, but all I need is to run a few functions sequentially.
我刚开始使用 node.js。我已经做了一些 ajax 的东西,但没有什么太复杂的,所以回调仍然有点超出我的头脑。我查看了 async,但我所需要的只是按顺序运行一些函数。
I basically have something that pulls some JSON from an API, creates a new one and then does something with that. Obviously, I can't just run it because it runs everything at once and has an empty JSON. Mostly the processes have to run sequentially, but if while pulling JSON from the API it can pull other JSON while it's waiting then that is fine. I just got confused when putting the callback in a loop. What do I do with the index? I think I have seen some places that use callbacks inside the loop as kind of a recursive function and don't use for loops at all.
我基本上有一些东西可以从 API 中提取一些 JSON,创建一个新的,然后用它做一些事情。显然,我不能只运行它,因为它一次运行所有内容并且有一个空的 JSON。大多数进程必须按顺序运行,但是如果在从 API 中提取 JSON 时它可以在等待时提取其他 JSON,那就没问题了。将回调放入循环中时,我只是感到困惑。我如何处理索引?我想我已经看到一些地方在循环内使用回调作为一种递归函数并且根本不使用 for 循环。
Simple examples would help a lot.
简单的例子会有很大帮助。
回答by T.J. Crowder
If the callback is defined in the same scope the loop is defined in (which is frequently the case), then the callback will have access to the index variable. Leaving aside NodeJS particulars for a moment, let's consider this function:
如果回调是在定义循环的同一范围内定义的(这种情况经常发生),则回调将可以访问索引变量。暂时抛开 NodeJS 的细节,让我们考虑一下这个函数:
function doSomething(callback) {
callback();
}
That function accepts a callback function reference and all it does is call it. Not very exciting. :-)
该函数接受一个回调函数引用,它所做的就是调用它。不是很令人兴奋。:-)
Now let's use that in a loop:
现在让我们在循环中使用它:
var index;
for (index = 0; index < 3; ++index) {
doSomething(function() {
console.log("index = " + index);
});
}
(In compute-intensive code — like a server process — best not to literally do the above in production code, we'll come back to that in a moment.)
(在计算密集型代码中——比如服务器进程——最好不要在生产代码中逐字执行上述操作,我们稍后会回到这一点。)
Now, when we run that, we see the expected output:
现在,当我们运行它时,我们会看到预期的输出:
index = 0
index = 1
index = 2
Our callback was able to access index, because the callback is a closureover the data in scope where it's defined. (Don't worry about the term "closure," closures are not complicated.)
我们的回调能够访问index,因为回调是对其定义范围内的数据的闭包。(不要担心术语“闭包”,闭包并不复杂。)
The reason I said it's probably best not to literally do the above in compute-intensive production code is that the code creates a function on every iteration(barring fancy optimization in the compiler, and V8 is very clever, but optimizing out creating those functions is non-trivial). So here's a slightly reworked example:
我说最好不要在计算密集型生产代码中逐字执行上述操作的原因是代码在每次迭代时都会创建一个函数(除非在编译器中进行花哨的优化,而且 V8 非常聪明,但是优化创建这些函数是非平凡)。所以这里有一个稍微修改的例子:
var index;
for (index = 0; index < 3; ++index) {
doSomething(doSomethingCallback);
}
function doSomethingCallback() {
console.log("index = " + index);
}
This may look a bit surprising, but it still works the same way, and still has the same output, because doSomethingCallbackis still a closure over index, so it still sees the value of indexas of when it's called. But now there's only one doSomethingCallbackfunction, rather than a fresh one on every loop.
这可能看起来有点令人惊讶,但它仍然以相同的方式工作,并且仍然具有相同的输出,因为doSomethingCallback仍然是一个闭包index,所以它仍然看到index调用它时的值。但是现在只有一个doSomethingCallback函数,而不是每个循环都有一个新函数。
Now let's take a negative example, something that doesn'twork:
现在,让我们一个反面的例子,一些不工作:
foo();
function foo() {
var index;
for (index = 0; index < 3; ++index) {
doSomething(myCallback);
}
}
function myCallback() {
console.log("index = " + index); // <== Error
}
That fails, because myCallbackis not defined in the same scope (or a nested scope) that indexis in defined in, and so indexis undefined within myCallback.
那失败了,因为myCallback没有在定义的同一范围(或嵌套范围)index中定义,因此index在myCallback.
Finally, let's consider setting up event handlers in a loop, because one has to be careful with that. Here we will dive into NodeJS a bit:
最后,让我们考虑在循环中设置事件处理程序,因为必须小心处理。在这里,我们将深入了解 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
});
}
It seemslike the above should work the same way that our earlier loops did, but there's a crucial difference. In our earlier loops, the callback was being called immediately, and so it saw the correct indexvalue because indexhadn't had a chance to move on yet. In the above, though, we're going to spin through the loop before the callback is called. The result? We see
它看起来像上面应该工作相同的方式,我们前面的循环一样,但有一个关键的区别。在我们之前的循环中,回调被立即调用,因此它看到了正确的index值,因为index还没有机会继续。但是,在上面,我们将在调用回调之前旋转循环。结果?我们看
Process index 3 exited
Process index 3 exited
Process index 3 exited
This is a crucial point. A closure doesn't have a copyof the data it closes over, it has a live referenceto it. So by the time the exitcallback on each of those processes gets run, the loop will already be complete, so all three calls see the same indexvalue (its value as of the endof the loop).
这是一个关键点。闭包没有它关闭的数据的副本,它有一个对它的实时引用。因此,当exit每个进程的回调运行时,循环已经完成,因此所有三个调用都会看到相同的index值(它在循环结束时的值)。
We can fix this by having the callback use a differentvariable that won't change, like this:
我们可以通过让回调使用一个不会改变的不同变量来解决这个问题,如下所示:
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");
};
}
Now we output the correct values (in whatever order the processes exit):
现在我们输出正确的值(无论进程退出的顺序如何):
Process index 1 exited
Process index 2 exited
Process index 0 exited
The way that works is that the callback we assign to the exitevent closes over the iargument in the call we make to makeExitCallback. The first callback that makeExitCallbackcreates and returns closes over the ivalue for that call to makeExitCallback, the second callback it creates closes over the ivalue for thatcall to makeExitCallback(which is different than the ivalue for the earlier call), etc.
工作的方式是我们分配给exit事件的回调在我们i调用的参数上关闭makeExitCallback。第一回调makeExitCallback创建并返回关闭在i该呼叫到值makeExitCallback,它在创建关闭第二回调i为值该呼叫makeExitCallback(这是比不同i的先前调用值)等。
If you give the article linked abovea read, a number of things should be clearer. The terminology in the article is a bit dated (ECMAScript 5 uses updated terminology), but the concepts haven't changed.
如果你阅读上面链接的文章,很多事情应该更清楚。文章中的术语有点过时(ECMAScript 5 使用更新的术语),但概念没有改变。

