在JavaScript中循环调用异步函数

64

我有以下代码:

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循环的最后一个值。

有人能告诉我我在闭包方面做错了什么吗?我以为我理解了它们,但我想不出为什么这不起作用。

10个回答

83

由于你正在遍历一个数组,所以你可以简单地使用forEach,它提供了回调函数中的列表项和索引。迭代会有它自己的作用域。

list.forEach(function(listItem, index){
  mc_cli.get(listItem, function(err, response) {
    do_something(index);
  });
});

2
@joseph,你的推理听起来很不错。你能解释一下这句话的意思吗:“迭代将有其自己的范围”? - Sandip Subedi
5
这就是 JavaScript 中闭包概念的应用,指的是内部函数作用域能够记住其所有引用,即使它们在函数作用域之外。你可以使用 console.dir(object) 在开发工具中查看闭包,或者在“do_something”函数上设置断点,并查看开发人员工具的右面板。想要了解更多,请参阅此处 => https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures. - Bose_geek

46

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

好的,很棒。所以所有异步函数都已经启动,并且循环退出。现在,由于它们是异步的,不确定这些函数何时会完成或以什么顺序完成。如果您有需要等待所有这些函数完成后再执行的代码,我建议保持一个简单的计数器,以记录已完成的函数数量:

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

20

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

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可以看到它在Edge 13甚至Firefox 49(我没有在这些浏览器中检查)之前都不支持。它甚至说它在Node 4上也不受支持,但我个人测试了一下,似乎是支持的。


5
过去的24小时里,我一直在为了搞不明白为什么那该死的“for循环”不能像它应该的那样工作而苦恼。我一直都使用“var i = 0”,直到看到你的帖子。我将“var i = 0”改为“let i = 0”,所有事情都神奇地正常工作了。我该如何把我的声誉全部给你,你值得拥有它们... - Hafiz Temuri
2
let 对我来说是获胜者。 - Jeff Beagley

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

6
尝试使用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()函数。

2

ES2017: 你可以将异步代码包装在一个函数中(比如XHRPost),该函数返回一个Promise对象(承诺异步代码)。

然后,在for循环内使用神奇的Await关键字调用该函数(XHRPost)。 :)

let http = new XMLHttpRequest();
let url = 'http://sumersin/forum.social.json';

function XHRpost(i) {
    return new Promise(function(resolve) {
        let params = 'id=nobot&%3Aoperation=social%3AcreateForumPost&subject=Demo' + i + '&message=Here%20is%20the%20Demo&_charset_=UTF-8';
        http.open('POST', url, true);
        http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
        http.onreadystatechange = function() {
                console.log("Done " + i + "<<<<>>>>>" + http.readyState);
                if(http.readyState == 4){
                    console.log('SUCCESS :',i);
                    resolve();
                }
        }
        http.send(params);       
   });
}

for (let i = 1; i < 5; i++) {
    await XHRpost(i);
   }

0
如果你想在循环内运行异步函数,但仍然希望在回调执行后保留索引或其他变量,你可以将代码包装在IIFE(立即调用的函数表达式)中。
var arr = ['Hello', 'World', 'Javascript', 'Async', ':)'];
for( var i = 0; i < arr.length; i++) {
  (function(index){
    setTimeout(function(){
       console.log(arr[index]);
 }, 500);

0
(async function() {
  for(var i = 1; i < category.length; i++){

   let result = await $.ajax({
        url: '{{ route('admin.loyalty-commission.sub-category') }}', // Url of the Route
        
        data: {id:category[i-1], cat_state:i, next_cat:category[i]},
        success: function (data) {
            console.log('ajax success previous category id' + category[i-1]);

            // Check if the logic was successful or not
            if (data.status == 'success') {
                
                $(data.subcategories).appendTo(sub_category); //ajax result append to sub_category
            } else {
                console.log(data.msg);
            }
        },
        error: function (data) {
            // Error while calling the controller (HTTP Response Code different as 200 OK
            console.log('Error:', data);
            success = false;
        }
    });
 }
})()

-1

这是我应用程序中的示例代码。它可能有助于解决问题。 我在 map 循环中使用了 async/await。多个 promises 解析到一个数组中。

这帮助我解决了这个问题 JavaScript 中循环中的 async 和 await

 const refObjId= ['Account', 'Contact', 'Group'];

 const readdirPro = file => {
   return new Promise((resolve, reject) => {
    fs.readdir(file, (err, data) => {
      if (err) reject('I could not find the file');
      resolve(data);
    });
  });
};

const fileNamePromises = refObjId.map(async el => {
    const fileName = await readdirPro(`${__dirname}/../csv-files/${el}/data`);
    return fileName;
  });

//fileNamePromises is an array of promises


  const fileArr = await Promise.all(fileNamePromises);
    console.log(fileArr);

-2

使用ES6(typescript)可以利用asyncawait的优势:

let list: number[] = [1, 2, 3, 4, 5];

// this is async fucntion
function do_something(counter: number): Promise<number> {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('called after ' + counter + ' seconds');
            resolve(counter);
        }, counter * 1000);
    })
}
async function foo() {
    // itrate over list and wait for when everything is finished
    let data = await Promise.all(list.map(async i => await do_something(i)));

    console.log(data);
}

foo();

使用ES6(typescript),typescript?? async/await 是该语言的一部分! - Dan Starns

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