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

IT技术 javascript asynchronous for-loop closures
2021-02-07 18:50:24

我有以下代码:

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

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

我尝试用这种方式关闭

do_something((function(x){return x})(i)) 

但显然这再次始终使用 for 循环索引的最后一个值。

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

var create_closure = function(i) {
    return function() {
        return i;
    }
}

然后打电话

do_something(create_closure(i)())

但同样没有成功,返回值始终是 for 循环的最后一个值。

谁能告诉我关闭我做错了什么?我以为我理解它们,但我不明白为什么这不起作用。

6个回答

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

list.forEach(function(listItem, index){
  mc_cli.get(listItem, function(err, response) {
    do_something(index);
  });
});
@SandipSubedi - 这是 javascript 中闭包的概念发挥作用的地方。这是内部函数作用域能够记住所有引用的能力,即使它们在函数作用域之外。您可以使用 console.dir(object) 在开发工具中查看闭包,或者可以通过简单地在“do_something”函数上放置断点并查看开发工具的右侧窗格来查看闭包。您可以在此处阅读更多相关信息 => developer.mozilla.org/en-US/docs/Web/JavaScript/Closures
2021-03-19 18:50:24
@joseph 你的推理听起来很棒。你能解释一下我的这一部分“迭代将有自己的范围”吗?
2021-04-08 18:50:24

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

好,太棒了。至此所有异步函数都启动完毕,循环退出。现在,由于它们的异步性质,不知道这些函数何时完成,或者它们将按什么顺序完成。如果您的代码需要等到所有这些函数都完成后再执行,我建议您简单计算一下已经完成的函数数:

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 :).');
}

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

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

但无论如何,最好forEach使用立即调用函数来使用或创建闭包,因为let是 ES2015 特性,可能不支持所有浏览器和实现。这里Bindings ->let->for/for-in loop iteration scope我可以看到它不支持,直到边缘13,甚至直到火狐49(我还没有在这些浏览器中检查)。它甚至说 Node 4 不支持它,但我亲自测试过,它似乎受支持。

在过去的 24 小时里,我一直在用头撞墙,因为我不明白为什么这个他妈的国王for loop没有像预期的那样工作。我一直在使用var i = 0这个,直到我看到你的帖子。我更改var i = 0let i = 0,一切都神奇地正常工作。我怎么能把我所有的名声都给你,你应得的……
2021-03-20 18:50:24
let 是我的赢家
2021-03-22 18:50:24

你已经很接近了,但你应该将闭包传递给get而不是将它放在回调中:

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


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

试试这个,使用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()
            })
        })
    }
})()

这将在每个循环中停止循环,直到next()函数被触发