Асинхронный процесс внутри цикла 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); } })();