2012-07-14 19 views
61

estoy corriendo un bucle de eventos de la siguiente forma:proceso asíncrono dentro de un bucle Javascript para

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

    asycronouseProcess(callBackFunction() { 
     alert(i); 
    }); 
} 

Lo que me gustaría que esto es mostrar una serie de alertas que muestran los números del 0 al 10. El problema es que cuando se activa la función de devolución de llamada, el ciclo ya ha pasado por algunas iteraciones y muestra un valor más alto de i. ¿Alguna recomendación sobre cómo solucionar esto?

+0

¿Qué tal si agregamos el parámetro i a la función 'asynchronousProcess'? Que puede pasarlo a la función callbackFunction –

+1

duplicación posible de [Javascript closure inside loops - ejemplo práctico simple] (http://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example) –

Respuesta

114

El bucle for se ejecuta inmediatamente hasta su finalización mientras se inician todas las operaciones asincrónicas. Cuando completen algún tiempo en el futuro y llamen a sus devoluciones de llamada, el valor de su variable de índice de bucle i estará en su último valor para todas las devoluciones de llamada.

Esto se debe a que el bucle for no espera a que finalice una operación asincrónica antes de continuar con la siguiente iteración del bucle y porque las devoluciones de llamada asíncronas se invocan en el futuro. Por lo tanto, el bucle completa sus iteraciones y ENTONCES se llaman las devoluciones de llamada cuando terminan esas operaciones asincrónicas. Como tal, el índice de bucle está "listo" y se encuentra en su valor final para todas las devoluciones de llamada.

Para solucionar esto, debe guardar de forma exclusiva el índice de bucle por separado para cada devolución de llamada. En Javascript, la forma de hacerlo es capturarlo en un cierre de función. Esto se puede hacer creando un cierre de función en línea específicamente para este propósito (el primer ejemplo se muestra a continuación) o puede crear una función externa a la que le pase el índice y dejar que mantenga el índice exclusivamente para usted (segundo ejemplo se muestra a continuación).

A partir de 2016, si tiene una ejecución de Javascript totalmente hasta a la especificación ES6, también se puede utilizar let para definir la variable de bucle for y se definirá de forma única para cada iteración del bucle for (tercera aplicación abajo). Sin embargo, tenga en cuenta que esta es una característica de implementación tardía en las implementaciones de ES6, por lo que debe asegurarse de que su entorno de ejecución sea compatible con esa opción.

Uso.forEach() para iterar ya que crea su propio cierre de función

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

Crear su propio cierre de función Utilizando 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); 
} 

crear o modificar Función externa y pasarlo de la variable de

Si puede modificar la función asynchronousProcess(), entonces puede simplemente pasar el valor allí y tener el asynchronousProcess() funcionan de la CNTR de nuevo a la devolución de llamada como esto:

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

Uso ES6 let

Si tiene un entorno de ejecución de Javascript que es totalmente compatible con ES6, puede utilizar let en su for bucle de la siguiente manera:

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

let declaró en una declaración for bucle como esto creará una uni que valor de i para cada invocación del bucle (que es lo que desea).

de números de serie de promesas y asíncrono/esperan

Si su función asíncrona devuelve una promesa, y desea realizar una serie sus operaciones asíncronas para ejecutar uno tras otro en lugar de en paralelo y que se está ejecutando en un moderno entorno que admite async y await, entonces usted tiene más opciones.

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

Esto se asegurará de que sólo una llamada a asynchronousProcess() está en vuelo a la vez y el bucle for ni siquiera avanzará hasta que cada uno se hace. Esto es diferente a los esquemas anteriores que ejecutaron todas sus operaciones asincrónicas en paralelo, por lo que depende completamente del diseño que desee. Nota: await funciona con una promesa, por lo que su función debe devolver una promesa que se resuelve/rechaza cuando se completa la operación asincrónica. Además, tenga en cuenta que para usar await, la función contenedora debe declararse async.

+1

Se agregó una segunda opción si puede modificar la función 'asycronouseProcess()'. – jfriend00

+0

¡Impresionante! Esto me ayudó mucho con CasperJS. – Manu

+0

Se agregó la implementación 'let' de ES6. – jfriend00

0

El código de JavaScript se ejecuta en un solo subproceso, por lo que no se puede bloquear principalmente para esperar a que se complete la primera iteración del ciclo antes de comenzar el siguiente sin afectar seriamente la usabilidad de la página.

La solución depende de lo que realmente necesita. Si el ejemplo está cerca de exactamente lo que necesita, la sugerencia de @ Simon de pasar i a su proceso asíncrono es buena.

9

¿Alguna recomendación sobre cómo solucionar esto?

Varios.Puede utilizar bind:

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

O, si su navegador soporta let (Será en la próxima versión de ECMAScript, sin embargo Firefox ya lo apoya desde hace tiempo) que podría tener:

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

O , se puede hacer el trabajo de bind manualmente (en caso de que el navegador no lo soporta, pero yo diría que se puede implementar una cuña en ese caso, que debe estar en el siguiente enlace):

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

Normalmente prefiero let cuando puedo usarlo (p. para el complemento de Firefox); de lo contrario, bind o una función personalizada currying (que no necesita un objeto de contexto).

+0

El ejemplo de ECMAScript es muy bueno para demostrar lo que 'let' puede hacer. – hazelnut

+0

¿Es 'asyncronouseProcess' en todas las respuestas algún tipo de marcador de posición? Me estoy "no definido". – JackHasaKeyboard

+0

El 'asyncronouseProcess' es parte de la pregunta original, entonces sí, es normal si te da" no definido ". Puede reemplazarlo con cualquier función asíncrona si desea verificar el problema original y cómo funciona la solución propuesta. Por ejemplo: 'function asycronouseProcess (fn) {setTimeout (fn, 100);}' – ZER0

5

async await es aquí (ES7), por lo que puede hacer este tipo de cosas muy fácilmente ahora.

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

Recuerde, esto sólo funciona si asycronouseProcess devuelve un Promise

Si asycronouseProcess no está bajo su control, entonces puede hacer que devuelva un Promise por sí mismo como esto

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

A continuación, reemplace esta línea await asycronouseProcess(); por await asyncProcess();

Comprender Promises antes incluso de mirar en async await es imprescindible (Lea también sobre la compatibilidad con async await)

0

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

Aquí es un enfoque funcional de la muestra a lo que se espera aquí.

Cuestiones relacionadas