Ответ 1
Цикл for
запускается немедленно до завершения, пока все ваши асинхронные операции запущены. Когда они завершат некоторое время в будущем и вызовет их обратные вызовы, значение переменной индекса цикла i
будет иметь последнее значение для всех обратных вызовов.
Это связано с тем, что цикл for
не ожидает завершения асинхронной операции до продолжения следующей итерации цикла и потому, что асинхронные обратные вызовы вызываются в будущем. Таким образом, цикл завершает свои итерации, и THEN вызывающие вызовы вызываются при завершении этих операций async. Таким образом, индекс цикла "сделан" и сидит в своем окончательном значении для всех обратных вызовов.
Чтобы обойти это, вы должны однозначно сохранять индекс цикла отдельно для каждого обратного вызова. В Javascript способ сделать это - захватить его при закрытии функции. Это может быть сделано для создания закрытого закрытия функции специально для этой цели (первый пример, показанный ниже), или вы можете создать внешнюю функцию, через которую вы передаете индекс, и позволить ему поддерживать индекс уникально для вас (второй пример показан ниже).
Начиная с 2016 года, если у вас есть полная реализация Javascript для ES6, вы можете также использовать let
для определения переменной цикла for
и она будет однозначно определена для каждой итерации цикла for
(третья реализация ниже). Но обратите внимание, что это поздняя реализация в реализациях ES6, поэтому вам нужно убедиться, что среда выполнения поддерживает эту опцию.
Используйте.forEach() для повторения, так как он создает свое собственное закрытие функции
someArray.forEach(function(item, i) {
asynchronousProcess(function(item) {
console.log(i);
});
});
Создайте собственное закрытие функции с помощью IIFE
var j = 10;
for (var i = 0; i < j; i++) {
(function(cntr) {
// here the value of i was passed into as the argument cntr
// and will be captured in this function closure so each
// iteration of the loop can have it own value
asynchronousProcess(function() {
console.log(cntr);
});
})(i);
}
Создайте или измените внешнюю функцию и передайте ей переменную
Если вы можете изменить функцию asynchronousProcess()
, вы можете просто передать это значение и использовать функцию asynchronousProcess()
cntr для обратного вызова следующим образом:
var j = 10;
for (var i = 0; i < j; i++) {
asynchronousProcess(i, function(cntr) {
console.log(cntr);
});
}
Использовать ES6 let
Если у вас есть среда исполнения Javascript, которая полностью поддерживает ES6, вы можете использовать let
в своем цикле for
следующим образом:
const j = 10;
for (let i = 0; i < j; i++) {
asynchronousProcess(function() {
console.log(i);
});
}
let
объявлено в for
цикла декларации, как это создаст уникальное значение i
для каждого вызова цикла (который является то, что вы хотите).
Сериализация с обещаниями и асинхронный просмотр/ожидание
Если функция async возвращает обещание, и вы хотите сериализовать операции async для запуска один за другим, а не параллельно, и вы работаете в современной среде, поддерживающей async
и await
, у вас есть больше возможностей.
async function someFunction() {
const j = 10;
for (let i = 0; i < j; i++) {
// wait for the promise to resolve before advancing the for loop
await asynchronousProcess();
console.log(i);
}
}
Это гарантирует, что только один вызов asynchronousProcess()
находится в полете за раз, а цикл for
не будет продвигаться, пока не будет выполнено одно из них. Это отличается от предыдущих схем, которые выполняли ваши асинхронные операции параллельно, поэтому они полностью зависят от того, какой дизайн вы хотите. Примечание: await
работает с обещанием, поэтому ваша функция должна вернуть обещание, которое будет разрешено/отклонено при завершении асинхронной операции. Также обратите внимание, что для того, чтобы использовать await
, содержащая функция должна быть объявлена async
.