在JavaScript中的for循环中调用异步函数

Mas*_*iar 54 javascript closures asynchronous for-loop

我有以下代码:

for(var i = 0; i < list.length; i++){
    mc_cli.get(list[i], function(err, response) {
        do_something(i);
    });
}
Run Code Online (Sandbox Code Playgroud)

mc_cli是与memcached数据库的连接.可以想象,回调函数是异步的,因此可以在for循环结束时执行.此外,以这种方式调用时,do_something(i)它始终使用for循环的最后一个值.

我用这种方式尝试了一个闭包

do_something((function(x){return x})(i)) 
Run Code Online (Sandbox Code Playgroud)

但显然这又是使用for循环索引的最后一个值.

我也尝试在for循环之前声明一个函数,如下所示:

var create_closure = function(i) {
    return function() {
        return i;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后打电话

do_something(create_closure(i)())
Run Code Online (Sandbox Code Playgroud)

但是再次没有成功,返回值始终是for循环的最后一个值.

任何人都可以告诉我,我的闭包装有什么问题吗?我以为我理解了它们,但我无法理解为什么这不起作用.

Jos*_*eph 79

由于您正在运行数组,因此您可以简单地使用 forEach哪个提供列表项,以及回调中的索引.迭代将有自己的范围.

list.forEach(function(listItem, index){
  mc_cli.get(listItem, function(err, response) {
    do_something(index);
  });
});
Run Code Online (Sandbox Code Playgroud)

  • @SandipSubedi-这是javascript中的闭包概念所在。内部函数作用域能够记住其所有引用,即使它们在函数作用域之外也是如此。您可以使用console.dir(object)在开发工具中查看闭包,也可以仅在'do_something'函数中放置断点并查看开发人员工具的右窗格。您可以在这里=&gt; https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures了解更多信息。 (4认同)
  • @joseph,您的推理听起来很棒。您能否解释一下我的这一部分“迭代将有其自身的范围”? (2认同)

小智 43

这是异步函数在循环内的范例,我通常使用立即调用的匿名函数来处理它.这确保了使用索引变量的正确值调用异步函数.

好,太棒了.所以所有异步函数都已启动,循环退出.现在,由于它们的异步性质或它们将以何种顺序完成,因此无法确定这些功能何时完成.如果你有代码需要等到所有这些函数在执行之前完成,我建议保持一个简单的计数已完成的函数数:

var total = parsed_result.list.length;
var count = 0;

for(var i = 0; i < total; i++){
    (function(foo){
        mc_cli.get(parsed_result.list[foo], function(err, response) {
            do_something(foo);
            count++;
            if (count > total - 1) done();
        });
    }(i));
}

// You can guarantee that this function will not be called until ALL of the
// asynchronous functions have completed.
function done() {
    console.log('All data has been loaded :).');
}
Run Code Online (Sandbox Code Playgroud)


vik*_*war 15

我知道这是一个老线程,但无论如何添加我的答案.ES2015 let具有在每次迭代时重新绑定循环变量的功能,因此它在异步回调中维护循环变量的值,因此您可以尝试以下方法:

for(let i = 0; i < list.length; i++){
    mc_cli.get(list[i], function(err, response) {
        do_something(i);
    });
}
Run Code Online (Sandbox Code Playgroud)

但无论如何,最好使用forEach或使用立即调用的函数创建一个闭包,因为它let是ES2015功能,可能不支持所有浏览器和实现.从这里开始,Bindings ->let->for/for-in loop iteration scope我可以看到它在Edge 13之前不支持,甚至直到Firefox 49(我还没有在这些浏览器中检查过).它甚至表示它不支持Node 4,但我个人测试过它似乎得到了支持.

  • 我过去24个小时一直在墙上砸头,因为我无法弄清楚为什么“ for循环”无法按预期工作。我一直都在使用“ var i = 0”,直到看到您的帖子为止。我将“ var i = 0”更改为“ let i = 0”,并且一切正常。我怎么能给我我所有的声誉,你应有的一切... (2认同)
  • `让`是我的赢家 (2认同)

Den*_*nis 14

你非常接近,但你应该通过闭包get而不是把它放在回调中:

function createCallback(i) {
    return function(){
        do_something(i);
    }
}


for(var i = 0; i < list.length; i++){
    mc_cli.get(list[i], createCallback(i));
}
Run Code Online (Sandbox Code Playgroud)


Fer*_*jal 5

使用async/await语法和Promise

(async function() {
    for(var i = 0; i < list.length; i++){
        await new Promise(next => {
            mc_cli.get(list[i], function(err, response) {
                do_something(i); next()
            })
        })
    }
})()
Run Code Online (Sandbox Code Playgroud)

这将在每个周期中停止循环,直到next()触发功能