Necesito un bucle que espere una llamada asíncrona antes de continuar. Algo como:
for ( /* ... */ ) {
someFunction(param1, praram2, function(result) {
// Okay, for cycle could continue
})
}
alert("For cycle ended");
¿Cómo podría hacer esto? ¿Tienes alguna idea?
javascript
Casarse con Hoffser
fuente
fuente
( /* ... */ )
parece un monstruo y ahora tengo miedo :(Respuestas:
No puede mezclar síncrono y asíncrono en JavaScript si bloquea el script, bloquea el navegador.
Tienes que ir por el camino completo del evento aquí, afortunadamente podemos esconder las cosas feas.
EDITAR: actualizado el código.
function asyncLoop(iterations, func, callback) { var index = 0; var done = false; var loop = { next: function() { if (done) { return; } if (index < iterations) { index++; func(loop); } else { done = true; callback(); } }, iteration: function() { return index - 1; }, break: function() { done = true; callback(); } }; loop.next(); return loop; }
Esto nos proporcionará un asincrónico
loop
, por supuesto, puede modificarlo aún más para tomar, por ejemplo, una función para verificar la condición del bucle, etc.Ahora pasemos a la prueba:
function someFunction(a, b, callback) { console.log('Hey doing some stuff!'); callback(); } asyncLoop(10, function(loop) { someFunction(1, 2, function(result) { // log the iteration console.log(loop.iteration()); // Okay, for cycle could continue loop.next(); })}, function(){console.log('cycle ended')} );
Y la salida:
Hey doing some stuff! 0 Hey doing some stuff! 1 Hey doing some stuff! 2 Hey doing some stuff! 3 Hey doing some stuff! 4 Hey doing some stuff! 5 Hey doing some stuff! 6 Hey doing some stuff! 7 Hey doing some stuff! 8 Hey doing some stuff! 9 cycle ended
fuente
loop.break()
se supone que debo hacer. ¿Solo una forma de forzar la salida si quieres?Simplifiqué esto:
FUNCIÓN:
var asyncLoop = function(o){ var i=-1; var loop = function(){ i++; if(i==o.length){o.callback(); return;} o.functionToLoop(loop, i); } loop();//init }
USO:
asyncLoop({ length : 5, functionToLoop : function(loop, i){ setTimeout(function(){ document.write('Iteration ' + i + ' <br>'); loop(); },1000); }, callback : function(){ document.write('All done!'); } });
EJEMPLO: http://jsfiddle.net/NXTv7/8/
fuente
Una alternativa más limpia a lo que @Ivo ha sugerido sería una cola de método asíncrono , asumiendo que solo necesita hacer una llamada asíncrona para la colección.
(Vea esta publicación de Dustin Diaz para una explicación más detallada)
function Queue() { this._methods = []; this._response = null; this._flushed = false; } (function(Q){ Q.add = function (fn) { if (this._flushed) fn(this._response); else this._methods.push(fn); } Q.flush = function (response) { if (this._flushed) return; this._response = response; while (this._methods[0]) { this._methods.shift()(response); } this._flushed = true; } })(Queue.prototype);
Simplemente crea una nueva instancia de
Queue
, agrega las devoluciones de llamada que necesita y luego vacía la cola con la respuesta asíncrona.var queue = new Queue(); queue.add(function(results){ for (var result in results) { // normal loop operation here } }); someFunction(param1, param2, function(results) { queue.flush(results); }
Un beneficio adicional de este patrón es que puede agregar varias funciones a la cola en lugar de solo una.
Si tiene un objeto que contiene funciones de iterador, puede agregar soporte para esta cola detrás de escena y escribir código que parece sincrónico, pero no lo es:
MyClass.each(function(result){ ... })
simplemente escriba
each
para poner la función anónima en la cola en lugar de ejecutarla inmediatamente, y luego vacíe la cola cuando se complete su llamada asíncrona. Este es un patrón de diseño muy simple y poderoso.PD: si estás usando jQuery, ya tienes una cola de métodos asíncronos a tu disposición llamada jQuery.Deferred .
fuente
someFunction
que retrasar el resto del ciclo, su patrón configura una lista de funciones que se ejecutarán en orden y todas recibirán los resultados de una otra llamada a la función. Es un buen patrón, pero no creo que coincida con la pregunta en cuestión.setTimeout
. Corre el riesgo de un comportamiento no deseado si el código se ejecuta más rápido de lo previsto.setTimeout
qué pasa si la devolución de llamada toma solo la mitad del tiempo, bueno, el código se ejecuta más rápido nuevamente ... entonces, cuál es el punto? El "código" en el "bucle" todavía está en orden, si hace algunas cosas fuera de él antes de la devolución de llamada completa, ya está pidiendo problemas, pero nuevamente es de un solo subproceso, me cuesta encontrar un escenario dondesetTimeout
se rompería algo, sin más errores de diseño.También mire esta espléndida biblioteca caolan / async . Su
for
bucle se puede realizar fácilmente utilizando mapSeries o series .Podría publicar un código de muestra si su ejemplo tuviera más detalles.
fuente
También podemos utilizar la ayuda de jquery.Deferred. en este caso, la función asyncLoop se vería así:
asyncLoop = function(array, callback) { var nextElement, thisIteration; if (array.length > 0) nextElement = array.pop(); thisIteration = callback(nextElement); $.when(thisIteration).done(function(response) { // here we can check value of response in order to break or whatever if (array.length > 0) asyncLoop(array, collection, callback); }); };
la función de devolución de llamada se verá así:
addEntry = function(newEntry) { var deferred, duplicateEntry; // on the next line we can perform some check, which may cause async response. duplicateEntry = someCheckHere(); if (duplicateEntry === true) { deferred = $.Deferred(); // here we launch some other function (e.g. $.ajax or popup window) // which based on result must call deferred.resolve([opt args - response]) // when deferred.resolve is called "asyncLoop" will start new iteration // example function: exampleFunction(duplicateEntry, deferred); return deferred; } else { return someActionIfNotDuplicate(); } };
función de ejemplo que resuelve diferido:
function exampleFunction(entry, deffered){ openModal({ title: "what should we do with duplicate" options: [ {name:"Replace", action: function(){replace(entry);deffered.resolve(replace:true)}}, {name: "Keep Existing", action: function(){deffered.resolve(replace:false)}} ] }) }
fuente
He estado usando "setTimeout (Func, 0);" truco durante un año. Aquí hay una investigación reciente que escribí para explicar cómo acelerarlo un poco. Si solo quiere la respuesta, vaya al Paso 4. Los pasos 1 2 y 3 explican el razonamiento y la mecánica;
// In Depth Analysis of the setTimeout(Func,0) trick. //////// setTimeout(Func,0) Step 1 //////////// // setTimeout and setInterval impose a minimum // time limit of about 2 to 10 milliseconds. console.log("start"); var workCounter=0; var WorkHard = function() { if(workCounter>=2000) {console.log("done"); return;} workCounter++; setTimeout(WorkHard,0); }; // this take about 9 seconds // that works out to be about 4.5ms per iteration // Now there is a subtle rule here that you can tweak // This minimum is counted from the time the setTimeout was executed. // THEREFORE: console.log("start"); var workCounter=0; var WorkHard = function() { if(workCounter>=2000) {console.log("done"); return;} setTimeout(WorkHard,0); workCounter++; }; // This code is slightly faster because we register the setTimeout // a line of code earlier. Actually, the speed difference is immesurable // in this case, but the concept is true. Step 2 shows a measurable example. /////////////////////////////////////////////// //////// setTimeout(Func,0) Step 2 //////////// // Here is a measurable example of the concept covered in Step 1. var StartWork = function() { console.log("start"); var startTime = new Date(); var workCounter=0; var sum=0; var WorkHard = function() { if(workCounter>=2000) { var ms = (new Date()).getTime() - startTime.getTime(); console.log("done: sum=" + sum + " time=" + ms + "ms"); return; } for(var i=0; i<1500000; i++) {sum++;} workCounter++; setTimeout(WorkHard,0); }; WorkHard(); }; // This adds some difficulty to the work instead of just incrementing a number // This prints "done: sum=3000000000 time=18809ms". // So it took 18.8 seconds. var StartWork = function() { console.log("start"); var startTime = new Date(); var workCounter=0; var sum=0; var WorkHard = function() { if(workCounter>=2000) { var ms = (new Date()).getTime() - startTime.getTime(); console.log("done: sum=" + sum + " time=" + ms + "ms"); return; } setTimeout(WorkHard,0); for(var i=0; i<1500000; i++) {sum++;} workCounter++; }; WorkHard(); }; // Now, as we planned, we move the setTimeout to before the difficult part // This prints: "done: sum=3000000000 time=12680ms" // So it took 12.6 seconds. With a little math, (18.8-12.6)/2000 = 3.1ms // We have effectively shaved off 3.1ms of the original 4.5ms of dead time. // Assuming some of that time may be attributed to function calls and variable // instantiations, we have eliminated the wait time imposed by setTimeout. // LESSON LEARNED: If you want to use the setTimeout(Func,0) trick with high // performance in mind, make sure your function takes more than 4.5ms, and set // the next timeout at the start of your function, instead of the end. /////////////////////////////////////////////// //////// setTimeout(Func,0) Step 3 //////////// // The results of Step 2 are very educational, but it doesn't really tell us how to apply the // concept to the real world. Step 2 says "make sure your function takes more than 4.5ms". // No one makes functions that take 4.5ms. Functions either take a few microseconds, // or several seconds, or several minutes. This magic 4.5ms is unattainable. // To solve the problem, we introduce the concept of "Burn Time". // Lets assume that you can break up your difficult function into pieces that take // a few milliseconds or less to complete. Then the concept of Burn Time says, // "crunch several of the individual pieces until we reach 4.5ms, then exit" // Step 1 shows a function that is asyncronous, but takes 9 seconds to run. In reality // we could have easilly incremented workCounter 2000 times in under a millisecond. // So, duh, that should not be made asyncronous, its horrible. But what if you don't know // how many times you need to increment the number, maybe you need to run the loop 20 times, // maybe you need to run the loop 2 billion times. console.log("start"); var startTime = new Date(); var workCounter=0; for(var i=0; i<2000000000; i++) // 2 billion { workCounter++; } var ms = (new Date()).getTime() - startTime.getTime(); console.log("done: workCounter=" + workCounter + " time=" + ms + "ms"); // prints: "done: workCounter=2000000000 time=7214ms" // So it took 7.2 seconds. Can we break this up into smaller pieces? Yes. // I know, this is a retarded example, bear with me. console.log("start"); var startTime = new Date(); var workCounter=0; var each = function() { workCounter++; }; for(var i=0; i<20000000; i++) // 20 million { each(); } var ms = (new Date()).getTime() - startTime.getTime(); console.log("done: workCounter=" + workCounter + " time=" + ms + "ms"); // The easiest way is to break it up into 2 billion smaller pieces, each of which take // only several picoseconds to run. Ok, actually, I am reducing the number from 2 billion // to 20 million (100x less). Just adding a function call increases the complexity of the loop // 100 fold. Good lesson for some other topic. // prints: "done: workCounter=20000000 time=7648ms" // So it took 7.6 seconds, thats a good starting point. // Now, lets sprinkle in the async part with the burn concept console.log("start"); var startTime = new Date(); var workCounter=0; var index=0; var end = 20000000; var each = function() { workCounter++; }; var Work = function() { var burnTimeout = new Date(); burnTimeout.setTime(burnTimeout.getTime() + 4.5); // burnTimeout set to 4.5ms in the future while((new Date()) < burnTimeout) { if(index>=end) { var ms = (new Date()).getTime() - startTime.getTime(); console.log("done: workCounter=" + workCounter + " time=" + ms + "ms"); return; } each(); index++; } setTimeout(Work,0); }; // prints "done: workCounter=20000000 time=107119ms" // Sweet Jesus, I increased my 7.6 second function to 107.1 seconds. // But it does prevent the browser from locking up, So i guess thats a plus. // Again, the actual objective here is just to increment workCounter, so the overhead of all // the async garbage is huge in comparison. // Anyway, Lets start by taking advice from Step 2 and move the setTimeout above the hard part. console.log("start"); var startTime = new Date(); var workCounter=0; var index=0; var end = 20000000; var each = function() { workCounter++; }; var Work = function() { if(index>=end) {return;} setTimeout(Work,0); var burnTimeout = new Date(); burnTimeout.setTime(burnTimeout.getTime() + 4.5); // burnTimeout set to 4.5ms in the future while((new Date()) < burnTimeout) { if(index>=end) { var ms = (new Date()).getTime() - startTime.getTime(); console.log("done: workCounter=" + workCounter + " time=" + ms + "ms"); return; } each(); index++; } }; // This means we also have to check index right away because the last iteration will have nothing to do // prints "done: workCounter=20000000 time=52892ms" // So, it took 52.8 seconds. Improvement, but way slower than the native 7.6 seconds. // The Burn Time is the number you tweak to get a nice balance between native loop speed // and browser responsiveness. Lets change it from 4.5ms to 50ms, because we don't really need faster // than 50ms gui response. console.log("start"); var startTime = new Date(); var workCounter=0; var index=0; var end = 20000000; var each = function() { workCounter++; }; var Work = function() { if(index>=end) {return;} setTimeout(Work,0); var burnTimeout = new Date(); burnTimeout.setTime(burnTimeout.getTime() + 50); // burnTimeout set to 50ms in the future while((new Date()) < burnTimeout) { if(index>=end) { var ms = (new Date()).getTime() - startTime.getTime(); console.log("done: workCounter=" + workCounter + " time=" + ms + "ms"); return; } each(); index++; } }; // prints "done: workCounter=20000000 time=52272ms" // So it took 52.2 seconds. No real improvement here which proves that the imposed limits of setTimeout // have been eliminated as long as the burn time is anything over 4.5ms /////////////////////////////////////////////// //////// setTimeout(Func,0) Step 4 //////////// // The performance numbers from Step 3 seem pretty grim, but GUI responsiveness is often worth it. // Here is a short library that embodies these concepts and gives a descent interface. var WilkesAsyncBurn = function() { var Now = function() {return (new Date());}; var CreateFutureDate = function(milliseconds) { var t = Now(); t.setTime(t.getTime() + milliseconds); return t; }; var For = function(start, end, eachCallback, finalCallback, msBurnTime) { var i = start; var Each = function() { if(i==-1) {return;} //always does one last each with nothing to do setTimeout(Each,0); var burnTimeout = CreateFutureDate(msBurnTime); while(Now() < burnTimeout) { if(i>=end) {i=-1; finalCallback(); return;} eachCallback(i); i++; } }; Each(); }; var ForEach = function(array, eachCallback, finalCallback, msBurnTime) { var i = 0; var len = array.length; var Each = function() { if(i==-1) {return;} setTimeout(Each,0); var burnTimeout = CreateFutureDate(msBurnTime); while(Now() < burnTimeout) { if(i>=len) {i=-1; finalCallback(array); return;} eachCallback(i, array[i]); i++; } }; Each(); }; var pub = {}; pub.For = For; //eachCallback(index); finalCallback(); pub.ForEach = ForEach; //eachCallback(index,value); finalCallback(array); WilkesAsyncBurn = pub; }; /////////////////////////////////////////////// //////// setTimeout(Func,0) Step 5 //////////// // Here is an examples of how to use the library from Step 4. WilkesAsyncBurn(); // Init the library console.log("start"); var startTime = new Date(); var workCounter=0; var FuncEach = function() { if(workCounter%1000==0) { var s = "<div></div>"; var div = jQuery("*[class~=r1]"); div.append(s); } workCounter++; }; var FuncFinal = function() { var ms = (new Date()).getTime() - startTime.getTime(); console.log("done: workCounter=" + workCounter + " time=" + ms + "ms"); }; WilkesAsyncBurn.For(0,2000000,FuncEach,FuncFinal,50); // prints: "done: workCounter=20000000 time=149303ms" // Also appends a few thousand divs to the html page, about 20 at a time. // The browser is responsive the entire time, mission accomplished // LESSON LEARNED: If your code pieces are super tiny, like incrementing a number, or walking through // an array summing the numbers, then just putting it in an "each" function is going to kill you. // You can still use the concept here, but your "each" function should also have a for loop in it // where you burn a few hundred items manually. ///////////////////////////////////////////////
fuente
Dada una función de trabajo asincrónica
someFunction
que llamará a una función de resultado con unresult
argumento que diga si el ciclo debe continuar o no:// having: // function someFunction(param1, praram2, resultfunc)) // function done() { alert("For cycle ended"); } (function(f){ f(f) })(function(f){ someFunction("param1", "praram2", function(result){ if (result) f(f); // loop continues else done(); // loop ends }); })
Para comprobar si finalizar o no el ciclo, la función de trabajo
someFunction
puede reenviar la función de resultado a otras operaciones asincrónicas. Además, toda la expresión se puede encapsular en una función asincrónica tomando una funcióndone
como devolución de llamada.fuente
Si le gusta la respuesta de wilsonpage pero está más acostumbrado a usar la sintaxis de async.js, aquí hay una variación:
function asyncEach(iterableList, callback, done) { var i = -1, length = iterableList.length; function loop() { i++; if (i === length) { done(); return; } callback(iterableList[i], loop); } loop(); } asyncEach(['A', 'B', 'C'], function(item, callback) { setTimeout(function(){ document.write('Iteration ' + item + ' <br>'); callback(); }, 1000); }, function() { document.write('All done!'); });
La demostración se puede encontrar aquí: http://jsfiddle.net/NXTv7/8/
fuente
Aquí hay otro ejemplo que creo que es más legible que otros, donde envuelve su función asíncrona dentro de una función que toma una
done
función, el índice de bucle actual y el resultado (si lo hay) de la llamada asíncrona anterior:function (done, i, prevResult) { // perform async stuff // call "done(result)" in async callback // or after promise resolves }
Una vez que
done()
se invoca, desencadena la siguiente llamada asincrónica, pasando nuevamente la función done, el índice actual y el resultado anterior. Una vez que se complete todo el ciclocallback
, se invocará el ciclo proporcionado .Aquí hay un fragmento que puede ejecutar:
asyncLoop({ limit: 25, asyncLoopFunction: function(done, i, prevResult) { setTimeout(function() { console.log("Starting Iteration: ", i); console.log("Previous Result: ", prevResult); var result = i * 100; done(result); }, 1000); }, initialArgs: 'Hello', callback: function(result) { console.log('All Done. Final result: ', result); } }); function asyncLoop(obj) { var limit = obj.limit, asyncLoopFunction = obj.asyncLoopFunction, initialArgs = obj.initialArgs || {}, callback = obj.callback, i = 0; function done(result) { i++; if (i < limit) { triggerAsync(result); } else { callback(result); } } function triggerAsync(prevResult) { asyncLoopFunction(done, i, prevResult); } triggerAsync(initialArgs); // init }
fuente
Puede utilizar
async await
introducido en ES7:for ( /* ... */ ) { let result = await someFunction(param1, param2); } alert("For cycle ended");
¡Esto solo funciona si
someFunction
devuelve una promesa!Si
someFunction
no devuelve una Promesa, puede hacer que devuelva una Promesa usted mismo de esta manera:function asyncSomeFunction(param1,praram2) { return new Promise((resolve, reject) => { someFunction(praram1,praram2,(result)=>{ resolve(result); }) }) }
Luego reemplace esta línea
await someFunction(param1, param2);
porawait asynSomeFunction(param1, param2);
Por favor, comprenda las promesas antes de escribir el
async await
código.fuente
Unexpected await inside loop
.javascript
problema. Esa advertencia surge de sueslint
configuración. Siempre desactivar esa regla deeslint
porque en la mayoría de los lugares que realmente es menester esperar dentro del buclehttp://cuzztuts.blogspot.ro/2011/12/js-async-for-very-cool.html
EDITAR:
enlace de github: https://github.com/cuzzea/lib_repo/blob/master/cuzzea/js/functions/core/async_for.js
function async_for_each(object,settings){ var l=object.length; settings.limit = settings.limit || Math.round(l/100); settings.start = settings.start || 0; settings.timeout = settings.timeout || 1; for(var i=settings.start;i<l;i++){ if(i-settings.start>=settings.limit){ setTimeout(function(){ settings.start = i; async_for_each(object,settings) },settings.timeout); settings.limit_callback ? settings.limit_callback(i,l) : null; return false; }else{ settings.cbk ? settings.cbk(i,object[i]) : null; } } settings.end_cbk?settings.end_cbk():null; return true; }
Esta función le permite crear una ruptura porcentual en el ciclo for usando settings.limit. La propiedad limit es solo un número entero, pero cuando se establece como array.length * 0.1, esto hará que se llame a settings.limit_callback cada 10%.
/* * params: * object: the array to parse * settings_object: * cbk: function to call whenwhen object is found in array * params: i,object[i] * limit_calback: function to call when limit is reached * params: i, object_length * end_cbk: function to call when loop is finished * params: none * limit: number of iteration before breacking the for loop * default: object.length/100 * timeout: time until start of the for loop(ms) * default: 1 * start: the index from where to start the for loop * default: 0 */
ejemplo:
var a = []; a.length = 1000; async_for_each(a,{ limit_callback:function(i,l){console.log("loading %s/%s - %s%",i,l,Math.round(i*100/l))} });
fuente
Una solución basada en biblioteca de promesas:
/* Since this is an open question for JS I have used Kris Kowal's Q promises for the same */ var Q = require('q'); /* Your LOOP body @success is a parameter(s) you might pass */ var loopBody = function(success) { var d = Q.defer(); /* OR use your favorite promise library like $q in angular */ /* 'setTimeout' will ideally be your node-like callback with this signature ... (err, data) {} as shown, on success you should resolve on failure you should reject (as always ...) */ setTimeout(function(err, data) { if (!err) { d.resolve('success'); } else { d.reject('failure'); } }, 100); //100 ms used for illustration only return d.promise; }; /* function to call your loop body */ function loop(itr, fn) { var def = Q.defer(); if (itr <= 0) { def.reject({ status: "un-successful " }); } else { var next = loop.bind(undefined, itr - 1, fn); // 'next' is all there is to this var callback = fn.bind(undefined /*, a, b, c.... */ ); // in case you want to pass some parameters into your loop body def.promise = callback().then(def.resolve, next); } return def.promise; } /* USAGE: loop(iterations, function(){}) the second argument has to be thenable (in other words return a promise) NOTE: this loop will stop when loop body resolves to a success Example: Try to upload file 3 times. HURRAY (if successful) or log failed */ loop(4, loopBody).then(function() { //success handler console.log('HURRAY') }, function() { //failed console.log('failed'); });
fuente
Necesitaba llamar a algunos
X
tiempos de función asincrónica , cada iteración debe haber ocurrido después de que se realizó la anterior, así que escribí una pequeña biblioteca que se puede usar así:// https://codepen.io/anon/pen/MOvxaX?editors=0012 var loop = AsyncLoop(function(iteration, value){ console.log("Loop called with iteration and value set to: ", iteration, value); var random = Math.random()*500; if(random < 200) return false; return new Promise(function(resolve){ setTimeout(resolve.bind(null, random), random); }); }) .finished(function(){ console.log("Loop has ended"); });
Cada vez que se llama a la función de bucle definida por el usuario, tiene dos argumentos, índice de iteración y valor de retorno de llamada anterior.
Este es un ejemplo de salida:
"Loop called with iteration and value set to: " 0 null "Loop called with iteration and value set to: " 1 496.4137048207333 "Loop called with iteration and value set to: " 2 259.6020382449663 "Loop called with iteration and value set to: " 3 485.5400568702862 "Loop has ended"
fuente