使用 node.js 理解 javascript 回调的概念,尤其是在循环中 [英] understanding the concept of javascript callbacks with node.js, especially in loops

查看:18
本文介绍了使用 node.js 理解 javascript 回调的概念,尤其是在循环中的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我刚开始使用 node.js.我已经做了一些 ajax 的东西,但没有什么太复杂的,所以回调仍然有点超出我的头脑.我查看了 async,但我只需要按顺序运行几个函数.

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.

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

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.

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

Simple examples would help a lot.

推荐答案

如果回调定义在定义循环的同一范围内(这种情况经常发生),则回调将有权访问索引变量.暂时抛开 NodeJS 的细节,让我们考虑一下这个函数:

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:

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);
    });
}

(在计算密集型代码中        最好不要在生产代码中逐字地执行上述操作,我们稍后会回到这一点.)

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

Now, when we run that, we see the expected output:

index = 0
index = 1
index = 2

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

Our callback was able to access index, because the callback is a closure over the data in scope where it's defined. (Don't worry about the term "closure," closures are not complicated.)

我说最好不要在计算密集型生产代码中逐字执行上述操作的原因是代码在每次迭代上创建一个函数(除非编译器中进行了花哨的优化,而 V8 是非常聪明,但优化创建这些函数并非易事).所以这里有一个稍微修改的例子:

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:

var index;

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

function doSomethingCallback() {
    console.log("index = " + index);
}

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

This may look a bit surprising, but it still works the same way, and still has the same output, because doSomethingCallback is still a closure over index, so it still sees the value of index as of when it's called. But now there's only one doSomethingCallback function, rather than a fresh one on every loop.

现在让我们举一个反面的例子,它行不通:

Now let's take a negative example, something that doesn't work:

foo();

function foo() {
    var index;

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

function myCallback() {
    console.log("index = " + index); // <== Error
}

那失败了,因为 myCallback 没有定义在与 index 相同的范围(或嵌套范围)中,所以 indexmyCallback 中未定义.

That fails, because myCallback is not defined in the same scope (or a nested scope) that index is in defined in, and so index is undefined within myCallback.

最后,让我们考虑在循环中设置事件处理程序,因为必须小心处理.在这里,我们将深入了解 NodeJS:

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:

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 还没有机会继续.但是,在上面,我们将在调用回调之前旋转循环.结果?我们看到

It seems like 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 index value because index hadn'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

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

这是一个关键点.闭包没有它关闭的数据的副本,它有一个实时引用.因此,当每个进程的 exit 回调运行时,循环已经完成,因此所有三个调用都会看到相同的 index 值(其值截至循环的结束).

This is a crucial point. A closure doesn't have a copy of the data it closes over, it has a live reference to it. So by the time the exit callback on each of those processes gets run, the loop will already be complete, so all three calls see the same index value (its value as of the end of the loop).

我们可以通过让回调使用一个不会改变的不同变量来解决这个问题,就像这样:

We can fix this by having the callback use a different variable 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

工作方式是我们分配给 exit 事件的回调在我们对 makeExitCallback 的调用中通过 i 参数关闭.makeExitCallback 创建并返回的第一个回调在 i 值上关闭对 makeExitCallback 的调用,它创建的第二个回调在 上关闭那个调用makeExitCallback的>i值(不同于之前调用的i值),等等.

The way that works is that the callback we assign to the exit event closes over the i argument in the call we make to makeExitCallback. The first callback that makeExitCallback creates and returns closes over the i value for that call to makeExitCallback, the second callback it creates closes over the i value for that call to makeExitCallback (which is different than the i value for the earlier call), etc.

如果你给上面链接的文章阅读,许多事情应该更清楚.文章中的术语有点过时(ECMAScript 5 使用更新的术语),但概念没有改变.

If you give the article linked above a 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.

这篇关于使用 node.js 理解 javascript 回调的概念,尤其是在循环中的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆