¿Forma correcta de esperar a que finalice una función antes de continuar?
Tengo dos funciones JS. Uno llama al otro. Dentro de la función de llamada, me gustaría llamar a la otra, esperar a que finalice esa función y luego continuar. Entonces, por ejemplo/pseudocódigo:
function firstFunction(){
for(i=0;i<x;i++){
// do something
}
};
function secondFunction(){
firstFunction()
// now wait for firstFunction to finish...
// do something else
};
Se me ocurrió esta solución, pero no sé si es una forma inteligente de hacerlo.
var isPaused = false;
function firstFunction(){
isPaused = true;
for(i=0;i<x;i++){
// do something
}
isPaused = false;
};
function secondFunction(){
firstFunction()
function waitForIt(){
if (isPaused) {
setTimeout(function(){waitForIt()},100);
} else {
// go do that thing
};
}
};
¿Es eso legítimo? ¿Existe una forma más elegante de manejarlo? ¿Quizás con jQuery?
Una forma de lidiar con trabajos asincrónicos como este es usar una función de devolución de llamada, por ejemplo:
function firstFunction(_callback){
// do some asynchronous work
// and when the asynchronous stuff is complete
_callback();
}
function secondFunction(){
// call first function and pass in a callback function which
// first function runs when it has completed
firstFunction(function() {
console.log('huzzah, I\'m done!');
});
}
Según la sugerencia de @Janaka Pushpakumara, ahora puedes usar funciones de flecha para lograr lo mismo. Por ejemplo:
firstFunction(() => console.log('huzzah, I\'m done!'))
Actualización: respondí esto hace bastante tiempo y realmente quiero actualizarlo. Si bien las devoluciones de llamada están absolutamente bien, según mi experiencia, tienden a generar un código que es más difícil de leer y mantener. Sin embargo, hay situaciones en las que todavía los uso, como para pasar eventos en progreso y similares como parámetros. Esta actualización es sólo para enfatizar las alternativas.
Además, la pregunta original no menciona específicamente async, por lo que en caso de que alguien esté confundido, si su función es sincrónica, se bloqueará cuando se llame. Por ejemplo:
doSomething()
// the function below will wait until doSomething completes if it is synchronous
doSomethingElse()
Aunque, como está implícito, la función es asincrónica, la forma en que tiendo a lidiar con todo mi trabajo asincrónico hoy es con async/await. Por ejemplo:
const secondFunction = async () => {
const result = await firstFunction()
// do something else here after firstFunction completes
}
En mi opinión, async/await hace que su código sea mucho más legible que usar promesas directamente (la mayor parte de las veces). Si necesita controlar los errores de captura, utilícelo con try/catch. Lea más sobre esto aquí: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function .
Utilice asíncrono/espera:
async function firstFunction(){
for(i=0;i<x;i++){
// do something
}
return;
};
luego use await en su otra función para esperar a que regrese:
async function secondFunction(){
await firstFunction();
// now wait for firstFunction to finish...
// do something else
};
Una forma elegante de esperar a que se complete primero una función es usar Promesas con la función async/await .
- En primer lugar, cree una Promesa . La función que creé se completará después de 2 segundos. Lo usé
setTimeout
para demostrar la situación en la que las instrucciones tardarían algún tiempo en ejecutarse. - Para la segunda función, puede usar
async/await
una función donde deberá
await
completar la primera función antes de continuar con las instrucciones.
Ejemplo:
//1. Create a new function that returns a promise
function firstFunction() {
return new Promise((resolve, reject) => {
let y = 0
setTimeout(() => {
for (i=0; i<10; i++) {
y++
}
console.log('Loop completed.')
resolve(y)
}, 2000)
})
}
//2. Create an async function
async function secondFunction() {
console.log('Before promise call.')
//3. Await for the first function to complete
const result = await firstFunction()
console.log('Promise resolved: ' + result)
console.log('Next step.')
};
secondFunction()
Nota:
Podrías simplemente resolve
hacerlo Promise
sin ningún valor así resolve()
. En mi ejemplo, tengo resolved
el Promise
valor de y
that y luego puedo usarlo en la segunda función.
Aviso
Esta respuesta está desactualizada para los estándares actuales. async/await
Considere la posibilidad de utilizar promesas y sintaxis nativas de JavaScript .
Parece que te estás perdiendo un punto importante aquí: JavaScript es un entorno de ejecución de un solo subproceso. Miremos nuevamente su código, tenga en cuenta que agregué alert("Here")
:
var isPaused = false;
function firstFunction(){
isPaused = true;
for(i=0;i<x;i++){
// do something
}
isPaused = false;
};
function secondFunction(){
firstFunction()
alert("Here");
function waitForIt(){
if (isPaused) {
setTimeout(function(){waitForIt()},100);
} else {
// go do that thing
};
}
};
No tienes que esperar isPaused
. Cuando veas la alerta "Aquí", isPaused
ya estarás false
, y firstFunction
habrás regresado. Esto se debe a que no puede "ceder" desde dentro del for
bucle ( // do something
), es posible que el bucle no se interrumpa y primero tendrá que completarse por completo (más detalles: manejo de subprocesos de Javascript y condiciones de carrera ).
Dicho esto, aún puedes hacer que el código fluya internamente firstFunction
para que sea asíncrono y usar una devolución de llamada o una promesa para notificar a la persona que llama. Tendrías que renunciar al for
bucle y simularlo con if
( JSFiddle ):
function firstFunction()
{
var deferred = $.Deferred();
var i = 0;
var nextStep = function() {
if (i<10) {
// Do something
printOutput("Step: " + i);
i++;
setTimeout(nextStep, 500);
}
else {
deferred.resolve(i);
}
}
nextStep();
return deferred.promise();
}
function secondFunction()
{
var promise = firstFunction();
promise.then(function(result) {
printOutput("Result: " + result);
});
}
Como nota al margen, JavaScript 1.7 ha introducido yield
palabras clave como parte de los generadores . Eso permitirá "perforar" agujeros asincrónicos en un flujo de código JavaScript que de otro modo sería síncrono ( más detalles y un ejemplo ). Sin embargo, la compatibilidad del navegador con generadores está actualmente limitada a Firefox y Chrome, AFAIK.
Me pregunto por qué nadie ha mencionado este patrón simple. :
(function(next) {
//do something
next()
}(function() {
//do some more
}))
Usar tiempos de espera sólo para esperar ciegamente es una mala práctica; e involucrar promesas solo agrega más complejidad al código. En el caso de OP:
(function(next) {
for(i=0;i<x;i++){
// do something
if (i==x-1) next()
}
}(function() {
// now wait for firstFunction to finish...
// do something else
}))
una pequeña demostración -> http://jsfiddle.net/5jdeb93r/