Processo asincrono all’interno di un javascript per ciclo

Sto facendo funzionare un ciclo di eventi del seguente modulo:

var i; var j = 10; for (i = 0; i < j; i++) { asynchronousProcess(callbackFunction() { alert(i); }); } 

Sto cercando di visualizzare una serie di avvisi che mostrano i numeri da 0 a 10. Il problema è che quando viene triggersta la funzione di callback, il ciclo ha già attraversato alcune iterazioni e visualizza un valore più alto di i . Qualche consiglio su come risolvere questo problema?

Il ciclo for viene eseguito immediatamente fino al completamento mentre vengono avviate tutte le operazioni asincrone. Quando completano un po ‘di tempo in futuro e chiamano i loro callback, il valore della variabile indice del ciclo i sarà al suo ultimo valore per tutti i callback.

Questo perché il ciclo for non attende il completamento di un’operazione asincrona prima di proseguire con l’iterazione successiva del ciclo e poiché i callback asincroni vengono chiamati in futuro. Quindi, il ciclo completa le sue iterazioni e ALLORA i callback vengono richiamati al termine di quelle operazioni asincrone. In quanto tale, l’indice del ciclo viene “fatto” e seduto al suo valore finale per tutte le callback.

Per ovviare a questo, è necessario salvare in modo univoco l’indice del ciclo separatamente per ogni callback. In Javascript, il modo per farlo è catturarlo in una chiusura di funzione. Ciò può essere fatto creando una chiusura di funzione inline appositamente per questo scopo (primo esempio mostrato di seguito) oppure è ansible creare una funzione esterna a cui si passa l’indice e lasciare che mantenga l’indice in modo univoco per voi (secondo esempio mostrato di seguito).

A partire dal 2016, se si dispone di un’implementazione ES6 di JavaScript completamente specifica, è anche ansible utilizzare let per definire la variabile for loop e sarà definita in modo univoco per ogni iterazione del ciclo for (terza implementazione di seguito). Tuttavia, si noti che questa è una funzionalità di implementazione tardiva nelle implementazioni ES6, quindi è necessario assicurarsi che il proprio ambiente di esecuzione supporti tale opzione.

Usa. ForEach () per iterare poiché crea la propria chiusura di funzione

 someArray.forEach(function(item, i) { asynchronousProcess(function(item) { console.log(i); }); }); 

Crea la tua chiusura funzione personale Utilizzando un 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); } 

Crea o modifica la funzione esterna e passa la variabile

Se è ansible modificare la funzione asynchronousProcess() , è ansible semplicemente passare il valore in là e avere la funzione asynchronousProcess() restituendo il cntr al callback in questo modo:

 var j = 10; for (var i = 0; i < j; i++) { asynchronousProcess(i, function(cntr) { console.log(cntr); }); } 

Usa ES6 let

Se si dispone di un ambiente di esecuzione Javascript che supporta pienamente ES6, è ansible utilizzare let nel ciclo for questo modo:

 const j = 10; for (let i = 0; i < j; i++) { asynchronousProcess(function() { console.log(i); }); } 

let dichiarato in una dichiarazione for loop come questa creerà un valore univoco di i per ogni invocazione del ciclo (che è ciò che si desidera).

Serializzare con promesse e async / attendere

Se la tua funzione asincrona restituisce una promise e vuoi serializzare le tue operazioni asincrone per eseguirle una dopo l'altra invece che in parallelo e stai correndo in un ambiente moderno che supporta async e await , allora hai più opzioni.

 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); } } 

Ciò assicurerà che solo una chiamata a asynchronousProcess() sia in volo alla volta e che il ciclo for non avanzi nemmeno fino a quando non viene eseguito ciascuno. Questo è diverso dagli schemi precedenti che eseguivano tutte le operazioni asincrone in parallelo, quindi dipende interamente dal design che si desidera. Nota: await funziona con una promise, quindi la funzione deve restituire una promise risolta / rifiutata quando l'operazione asincrona è completa. Inoltre, tieni presente che per poter utilizzare await , la funzione di contenimento deve essere dichiarata async .

Qualche raccomandazione su come risolvere questo problema?

Parecchi. Puoi usare bind :

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

Oppure, se il tuo browser supporta let (sarà nella prossima versione di ECMAScript, comunque Firefox lo supporta già da un po ') potresti avere:

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

Oppure, puoi eseguire il lavoro manualmente (nel caso in cui il browser non lo supporti, ma direi che puoi implementare uno shim in quel caso, dovrebbe essere nel link sopra):

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

Di solito preferisco let quando posso usarlo (ad es. Per Firefox add-on); in caso contrario, o una funzione di elaborazione personalizzata (che non ha bisogno di un object di contesto).

async await is here (ES7), quindi puoi fare questo genere di cose molto facilmente ora.

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

Ricorda, questo funziona solo se asycronouseProcess sta restituendo una Promise

Se asycronouseProcess non è sotto il tuo controllo, puoi farlo restituire una Promise da te stesso in questo modo

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

Quindi sostituire questa riga await asycronouseProcess(); await asyncProcess();

Comprendere le Promises prima di async await anche in async await è necessario (leggi anche il supporto per l' 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(); 

Il codice JavaScript viene eseguito su un singolo thread, pertanto non è ansible bloccare principalmente l’attesa del completamento della prima iterazione del ciclo prima di iniziare il successivo senza incidere seriamente sull’usabilità della pagina.

La soluzione dipende da ciò di cui hai veramente bisogno. Se l’esempio si avvicina esattamente a quello che ti serve, il suggerimento di @ Simon di passare al tuo processo asincrono è buono.

ES2017: puoi racchiudere il codice asincrono all’interno di una funzione (ad esempio XHRPost) restituendo una promise (codice asincrono all’interno della promise).

Quindi chiama la funzione (XHRPost) all’interno del ciclo for ma con la magica parola chiave 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); } })();