Асинхронный процесс внутри цикла javascript for [дубликат]


этот вопрос уже есть ответ здесь:

Я запускаю цикл событий следующего вида:

var i;
var j = 10;
for (i = 0; i < j; i++) {

    asynchronousProcess(callbackFunction() {
        alert(i);
    });
}

Я пытаюсь отобразить серию предупреждений, показывающие цифры от 0 до 10. Проблема в том, что к тому времени функция обратного вызова срабатывает, цикл уже прошел несколько итераций и отображает более высокое значение i. Любые рекомендации о том, как это исправить?

6 77

6 ответов:

The for цикл выполняется немедленно до завершения, пока все ваши асинхронные операции запущены. Когда они завершат некоторое время в будущем и вызовут свои обратные вызовы, значение вашей переменной индекса цикла i будет последнее значение для всех обратных вызовов.

это так for цикл не ждет завершения асинхронной операции, прежде чем перейти к следующей итерации цикла и потому, что асинхронные обратные вызовы вызываются некоторое время в будущем. Таким образом, цикл завершает свои итерации, а затем обратные вызовы вызываются, когда эти асинхронные операции заканчиваются. Таким образом, индекс цикла "Сделано" и сидит на своем конечном значении для всех обратных вызовов.

чтобы обойти это, вы должны уникально сохранить индекс цикла отдельно для каждого обратного вызова. В JavaScript способ сделать это, чтобы захватить его в замыкание функции. Это можно либо сделать, либо создать встроенное закрытие функции специально для этой цели (первый пример показан ниже) или вы можете создать внешнюю функцию, которой вы передаете индекс, и позволить ей поддерживать индекс уникально для вас (второй пример показан ниже).

С 2016 года, если у вас есть полностью до спецификации ES6 реализации Javascript, вы также можете использовать let определение for переменная цикла и она будет однозначно определена для каждой итерации for цикл (третья реализация ниже). Но обратите внимание, что это поздняя функция реализации в ES6 реализации, поэтому вы должны убедиться, что ваша среда выполнения поддерживает этот параметр.

использовать .forEach () для итерации, так как он создает свою собственную функцию closure

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's 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 / await

если ваша асинхронная функция возвращает обещание, и вы хотите сериализовать свои асинхронные операции для запуска один за другим, а не параллельно, и вы работаете в современной среде, которая поддерживает 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.

любые рекомендации о том, как это исправить?

несколько. Вы можете использовать связать:

for (i = 0; i < j; i++) {
    asycronouseProcess(function (i) {
        alert(i);
    }.bind(null, i));
}

или, если Ваш браузер поддерживает пусть (это будет в следующей версии ECMAScript, однако Firefox уже поддерживает его с некоторых пор) вы могли бы иметь:

for (i = 0; i < j; i++) {
    let k = i;
    asycronouseProcess(function() {
        alert(k);
    });
}

или, вы могли бы сделать задание bind вручную (в случае если браузер не поддерживает это, но я бы сказал, что вы можете реализовать ШИМ в этом случае, она должна будьте в ссылке выше):

for (i = 0; i < j; i++) {
    asycronouseProcess(function(i) {
        return function () {
            alert(i)
        }
    }(i));
}

Я обычно предпочитаю let когда я могу использовать его (например, для дополнения Firefox); в противном случае bind или пользовательские карринг функция (которая не нуждается в объекте контекста).

async await здесь (ES7), так что теперь вы можете делать такие вещи очень легко.

  var i;
  var j = 10;
  for (i = 0; i < j; i++) {
    await asycronouseProcess();
    alert(i);
  }

помните, это работает только если asycronouseProcess возвращает a Promise

если asycronouseProcess не находится в вашем контроле, то вы можете сделать его вернуть Promise сам такой

function asyncProcess() {
  return new Promise((resolve, reject) => {
    asycronouseProcess(()=>{
      resolve();
    })
  })
}

затем замените эту строку await asycronouseProcess(); by await asyncProcess();

понимание Promises прежде чем даже не глядя на async await должен (Также прочитать о поддержке async await)

var i = 0;
var length = 10;

function for1() {
  console.log(i);
  for2();
}

function for2() {
  if (i == length) {
    return false;
  }
  setTimeout(function() {
    i++;
    for1();
  }, 500);
}
for1();

вот пример функционального подхода к тому, что ожидается здесь.

код JavaScript выполняется в одном потоке, поэтому вы не можете принципиально блокировать ожидание завершения первой итерации цикла перед началом следующей, не оказывая серьезного влияния на удобство использования страницы.

решение зависит от того, что вам действительно нужно. Если пример близок именно к тому, что вам нужно, @ Simon's suggestion to pass i для вашего асинхронного процесса это хорошо.

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

затем вызовите функцию (XHRPost) внутри цикла for, но с ключевым словом magical 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);       
    });
 }
 
(async () => {
    for (let i = 1; i < 5; i++) {
        await XHRpost(i);
       }
})();