Асинхронный процесс внутри цикла javascript for [дубликат]
этот вопрос уже есть ответ здесь:
Я запускаю цикл событий следующего вида:
var i;
var j = 10;
for (i = 0; i < j; i++) {
asynchronousProcess(callbackFunction() {
alert(i);
});
}
Я пытаюсь отобразить серию предупреждений, показывающие цифры от 0 до 10. Проблема в том, что к тому времени функция обратного вызова срабатывает, цикл уже прошел несколько итераций и отображает более высокое значение i
. Любые рекомендации о том, как это исправить?
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
возвращает aPromise
если
asycronouseProcess
не находится в вашем контроле, то вы можете сделать его вернутьPromise
сам такойfunction asyncProcess() { return new Promise((resolve, reject) => { asycronouseProcess(()=>{ resolve(); }) }) }
затем замените эту строку
await asycronouseProcess();
byawait 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); } })();