Node.JS:如何将变量传递给异步回调函数?

48

我相信我的问题基于对node.js异步编程的理解不足,以下是示例:

比如说:我有一个链接列表需要爬取。每当一个异步请求返回时,我想知道它是哪个URL。但由于竞态条件的存在,每个请求的URL都被设置为列表中最后一个值。

var links = ['http://google.com', 'http://yahoo.com'];
for (link in links) {
    var url = links[link];
    require('request')(url, function() {
        console.log(url);
    });
}

期望输出:

http://google.com
http://yahoo.com

实际输出:

http://yahoo.com
http://yahoo.com

所以我的问题要么是:

  1. 如何将url(按值)传递给回调函数?或
  2. 串联HTTP请求的正确方式是什么,使它们按顺序运行?或
  3. 我还漏掉了其他什么东西吗?

PS:对于1,我不想要一个检查回调参数的解决方案,而是一种回调了解“上面的”变量的一般方法。

3个回答

50

你的url变量没有被限定在for循环的范围内,因为JavaScript只支持全局和函数作用域。因此,你需要创建一个函数作用域来捕获循环中每次迭代的url值,可以使用立即执行的函数实现:

var links = ['http://google.com', 'http://yahoo.com'];
for (link in links) {
    (function(url) {
        require('request')(url, function() {
            console.log(url);
        });
    })(links[link]);
}

顺便说一句,在循环的中间嵌入require不是一个好的做法。可能需要重新编写成:

var request = require('request');
var links = ['http://google.com', 'http://yahoo.com'];
for (link in links) {
    (function(url) {
        request(url, function() {
            console.log(url);
        });
    })(links[link]);
}

3
不是关于范围的问题,而是关于闭包的问题。许多其他语言尚未拥有块级作用域,但由于缺乏闭包而不会遇到这个问题。 - slebetman
3
如果JavaScript支持块级作用域,那么该问题中回调函数中的闭包访问url将能够正常工作,因为循环的每次迭代都会获得自己的url变量(类似于C#)。请注意,这里的重点是保持原意并使翻译更加通俗易懂,不提供解释或其他非翻译内容。 - JohnnyHK
1
这里的 require 只是为了简洁起见,在我的代码中所有的 require 都在开头。 - Marc
如果我在回调函数中不使用for循环,它将返回未定义的url。 - Aero Wang

13

看看这个博客。可以使用.bind()方法传递变量到回调函数中。在你的情况下,应该是这样:

var links = ['http://google.com', 'http://yahoo.com'];
for (link in links) {
var url = links[link];

require('request')(url, function() {

    console.log(this.urlAsy);

}.bind({urlAsy:url}));
}

3
我更喜欢这种方法,因为它比将某些东西包装在一个函数中要简洁得多。 - Levi Roberts
1
这个可以不用for循环就能工作! - Aero Wang

7

有关此问题的一般讨论,请参见https://dev59.com/NmbWa4cB1Zd3GeqPaMEo#11747331

我建议使用类似以下的方法:

var links = ['http://google.com', 'http://yahoo.com'];

function createCallback(_url) {
    return function() {
        console.log(_url);
    }
};

for (link in links) {
    var url = links[link];
    require('request')(url, createCallback(url));
}

有很多更好的链接可以在StackOverflow上提供来解释这个问题。例如这个链接:https://dev59.com/k3A65IYBdhLWcg3w5zDB#3572616 - slebetman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接