Подтвердить что ты не робот

Вызов асинхронной функции в цикле for в JavaScript

У меня есть следующий код:

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.

Может ли кто-нибудь сказать мне, что я делаю неправильно с закрытием? Я думал, что понял их, но я не могу понять, почему это не работает.

4b9b3361

Ответ 1

Поскольку вы используете массив, вы можете просто использовать forEach, который предоставляет элемент списка, и индекс в обратном вызове. Итерация будет иметь свой собственный объем.

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

Ответ 2

Это парадигма асинхронной функции внутри-цикла, и я обычно общаюсь с ней с помощью функции invone-invoked-anonymous. Это гарантирует, что асинхронные функции вызываются с правильным значением индексной переменной.

Хорошо, отлично. Таким образом, все асинхронные функции запущены, и цикл завершается. Теперь нет никакой информации, когда эти функции будут завершены из-за их асинхронного характера или в каком порядке они будут завершены. Если у вас есть код, которому нужно подождать, пока все эти функции не будут выполнены до его выполнения, я рекомендую сохранить простой подсчет количества выполненных функций:

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

Ответ 3

Вы были довольно близки, но вы должны передать закрытие на get вместо того, чтобы помещать его в обратный вызов:

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


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

Ответ 4

Я знаю, что это старый поток, но все равно добавляю мой ответ. 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

Попробуйте это, используя синтаксис 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()

Ответ 6

ES2017: Вы можете обернуть асинхронный код внутри функции (скажем, XHRPost), возвращающей обещание (асинхронный код внутри обещания).

Затем вызовите функцию (XHRPost) внутри цикла for, но с волшебным ключевым словом Await. :)

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

Ответ 7

Если вы хотите запускать асинхронные функции внутри цикла, но по-прежнему хотите сохранять индекс или другие переменные после выполнения обратного вызова, вы можете заключить код в IIFE (выражение функции, вызываемое немедленно).

var arr = ['Hello', 'World', 'Javascript', 'Async', ':)'];
for( var i = 0; i < arr.length; i++) {
  (function(index){
    setTimeout(function(){
       console.log(arr[index]);
 }, 500);

Ответ 8

Используя ES6 (машинопись), вы можете использовать преимущества async и await:

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