Страницы

Поиск по вопросам

воскресенье, 24 ноября 2019 г.

Почему асинхронная функция внутри цикла выполняет последнюю итерацию много раз?


Есть setTimeout внутри цикла for:



for (var i = 1; i <= 5; i++) {
    setTimeout(function() {
        console.log(i);
    }, i * 1000);
}




Я хочу показывать числа 1, 2, 3, 4, 5, но показывает 6, 6, 6, 6, 6. Почему?
    


Ответы

Ответ 1



Дело в том, что функция выполняется после того, что цикл закончится. Поэтому, i уже равно 6, когда console.log(i) выполняется первый раз. Если еще непонятно, вот похожий пример в псевдокоде: У меня 1 камень. Через минуту скажи, сколько у меня камней. Дай мне камень сейчас. Через 2 минуты скажи, сколько у меня камней. Дай мне камень сейчас. Получится, что сейчас мне даст 2 камня, в итоге у меня будут 3. Через минуту скажет, сколько у меня камней (т.е. 3), и через две минуты опять скажет, что у меня 3 камня. Как это решить? Вставить асинхронную функцию в анонимную функцию, чтобы передать другое значени i функции вывода на каждой итерации. Это самое обычное решение. for(var i = 1; i <= 5; i++) { (function(i) { setTimeout(function() { console.log(i); }, i * 1000); })(i); } Создавать много одинаковых функций - не очень хорошо, можно поднять эту функцию выше: function pass(i) { return function () { console.log(i); } } for(var i = 1; i <= 5; i++) { setTimeout(pass(i), i * 1000); } Использовать рекурсию вместо цикла. Это тоже обычное решение. (function f(i) { if (i > 5) return; setTimeout(function() { console.log(i); f(i + 1); }, 1000); })(1); Использовать Function.prototype.bind(), чтобы создать новую функцию на каждой итерации. Это короче первого варианта, но IE8 и ниже не поддерживают .bind. for (var i = 1; i <= 5; i++) { setTimeout(function(i) { console.log(i); }.bind(null, i), i * 1000); } Передавать аргументы через setTimeout. Это поддерживают все современные браузеры, но если речь идёт о старых, то стоит проверить. for(var i = 1; i <= 5; i++) { setTimeout(function (i) { console.log(i); }, i * 1000, i); } Замечу, что теперь функции в цикле ничем не отличаются друг от друга, поэтому можно сделать одну функцию: function doSmth(i) { console.log(i); } for(var i = 1; i <= 5; i++) { setTimeout(doSmth, i * 1000, i); } Использовать let . Это удобный вариант, но это новая возможность в ECMAScript 2015 так что еще не работает в большинстве браузеров. Если хотите использовать ECMAScript 2015 до того, что браузеры его поддерживают, рекомендую попробовать Babel. Внимание: некоторые браузеры (например, IE 11) поддерживают let, но не поддерживают его в цикле for. for (let i = 1; i <= 5; i++) { setTimeout(function() { console.log(i); }, i * 1000); }

Ответ 2



Решение на async const intArray = [1, 2, 3, 4, 5]; async.eachSeries(intArray, function(i, callback) { setTimeout(function() { console.log(i); callback(); }, i * 1000); }, function(err) { // if any of the file processing produced an error, err would equal that error if( err ) { console.log('An error occured. All processing has stopped'); } else { console.log('Whole array processed successfully'); } });

Комментариев нет:

Отправить комментарий