Вызов асинхронной функции в цикле 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.
Может ли кто-нибудь сказать мне, что я делаю неправильно с закрытием? Я думал, что понял их, но я не могу понять, почему это не работает.
Ответы
Ответ 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();