web-dev-qa-db-ja.com

JavaScriptのforループ内で非同期関数を呼び出す

私は次のコードを持っています:

_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ループの最後の値になります。

誰かが私がクロージャーで間違っていることを教えてもらえますか?私はそれらを理解したと思ったが、なぜこれが機能しないのか理解できない。

52
Masiar

配列を実行しているので、リスト項目とコールバックのインデックスを提供するforEachを単純に使用できます。反復には独自のスコープがあります。

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

これは非同期関数内部ループのパラダイムであり、通常は即時呼び出し匿名関数を使用して処理します。これにより、インデックス変数の正しい値で非同期関数が呼び出されます。

いいですねしたがって、すべての非同期関数が起動され、ループが終了します。現在、これらの関数が非同期の性質のため、いつ完了するのか、いつ完了するのかはわかりません。これらのすべての関数が完了するまで待機してから実行する必要があるコードがある場合、終了した関数の数を単純に数えることをお勧めします。

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

私はこれが古いスレッドであることを知っていますが、とにかく私の答えを追加します。 ES2015 letには、反復ごとにループ変数を再バインドする機能があるため、非同期コールバックでループ変数の値が維持されるため、次のいずれかを試すことができます。

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

ただし、とにかく、forEachはES2015の機能であり、すべてのブラウザーと実装をサポートしているわけではないため、letを使用するか、即時呼び出し関数を使用してクロージャーを作成することをお勧めします。 From here under Bindings ->let->for/for-in loop iteration scopeEdge 13まで、およびFirefox 49までサポートされていないことがわかります(これらはチェックインしていませんブラウザ)。 Node 4ではサポートされていませんが、私は個人的にテストし、サポートされているようです。

15
vikneshwar

かなり近かったですが、クロージャーをコールバック内に置くのではなく、getに渡す必要があります。

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


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

_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()関数がトリガーされるまで、各サイクルでループを停止します

4

ES2017:非同期コードを関数(XHRPostなど)内にラップして、promise(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);
   }
2
Sumer

ループ内で非同期関数を実行したいが、コールバックが実行された後もインデックスまたは他の変数を保持したい場合は、コードを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
Amay Kulkarni