¿Forma correcta de esperar a que termine una función antes de continuar?

187

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 termine 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? ¿Hay alguna forma más elegante de manejarlo? Quizás con jQuery?

DA.
fuente
11
¿Qué hace firstFunctionexactamente eso lo hace asíncrono? De cualquier manera - verifique las promesas
zerkms
La primera función es actualizar rápidamente un reloj de puntaje cada 10 de segundo. Lo que ... ahora que lo pienso, supongo que podríamos calcular previamente y luego pausar manualmente la segunda llamada de función a través de setTimeout. Dicho esto, todavía puedo ver un deseo de tener una capacidad de pausa en otro lugar.
DA.
no necesitas una pausa - google for promises in jquery
zerkms
@zerkms promete parece interesante! Todavía estoy investigando el soporte del navegador ...
DA.
1
También tengo el mismo problema.
Vappor Washmade

Respuestas:

142

Una forma de lidiar con el trabajo asincrónico 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 puede usar las 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 son absolutamente buenas, en 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 pasar eventos en progreso y similares como parámetros. Esta actualización es solo para enfatizar las alternativas.

También la pregunta original no menciona specificallty asíncrona, por lo que en caso de que alguien se confunde, si su función es sincrónico, que se bloquean cuando se le llama. Por ejemplo:

doSomething()
// the function below will wait until doSomething completes if it is synchronous
doSomethingElse()

Si, como está implícito, la función es asíncrona, la forma en que tiendo a lidiar con todo mi trabajo asincrónico hoy es con async / wait. 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ía de las veces). Si necesita manejar errores de captura, úselo con try / catch. Lea más sobre esto aquí: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function .

Matt Way
fuente
9
@zerkms - ¿Quieres elaborar?
Matt Way
77
las devoluciones de llamada son inconvenientes para lidiar con la asincronía, las promesas son mucho más fáciles y flexibles
zerkms
1
Si bien tiene razón en que no debería haber usado la palabra mejor (actualizada), la conveniencia de las devoluciones de llamada frente a las promesas depende de la complejidad del problema.
Matt Way
3
Se convierte en algo fuera de tema, pero personalmente no veo una razón para preferir las devoluciones de llamada en estos días :-) No puedo recordar ninguno de mis códigos escritos en los últimos 2 años proporcionó devoluciones de llamadas sobre las promesas.
zerkms
3
Si lo desea, puede usar la función de flecha en es6 secondFunction () {firstFunction ((response) => {console.log (response);}); }
Janaka Pushpakumara
53

Use async / await:

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
};
Fawaz
fuente
9
Para aquellos de nosotros atascados en el soporte de navegadores antiguos, IE no admite async / wait
kyle
¿Se puede usar fuera de ambas funciones, como en $(function() { await firstFunction(); secondFunction(); });,?
Lewistrick
1
@Lewistrick Solo recuerda awaitque solo se puede usar dentro de un asyncmétodo. Entonces, si hace que su función principal asyncpueda llamar a todos los que estén async methodsdentro con o sin awaiteso, lo desea.
Fawaz
¿Es posible awaituna setTimeout?
Shayan
1
@Shayan sí, envuelve setTimeout en otra función que resuelve una promesa después del tiempo de espera.
Fawaz
51

Parece que te estás perdiendo un punto importante aquí: JavaScript es un entorno de ejecución de subproceso único. Veamos nuevamente su código, tenga en cuenta que he agregado 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 vea la alerta "Aquí", isPausedserá falseya, y firstFunctionse han devuelto. Esto se debe a que no puede "ceder" desde el interior del forbucle ( // do something), es posible que el bucle no se interrumpa y tendrá que completarse primero primero (más detalles: manejo de hilos Javascript y condiciones de carrera ).

Dicho esto, aún puede hacer que el código fluya firstFunctionpara que sea asíncrono y usar la devolución de llamada o la promesa de notificar a la persona que llama. Tendría que renunciar al forbucle 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);
    });
}

En una nota al margen, JavaScript 1.7 ha introducido yieldpalabras clave como parte de los generadores . Eso permitirá "perforar" agujeros asincrónicos en el flujo de código JavaScript sincrónico ( más detalles y un ejemplo ). Sin embargo, el soporte del navegador para generadores está actualmente limitado a Firefox y Chrome, AFAIK.

nariz
fuente
3
Me salvaste. $ .Deferred () es lo que he estado buscando. Gracias
Temitayo
@noseratio Intenté esto. Pero el método waitForIt no recibe ninguna llamada. ¿Qué estoy haciendo mal?
vigamage
@vigamage, ¿podría proporcionar un enlace a jsfiddle o codepen de lo que ha intentado?
noseratio
1
Hola, gracias por tu preocupación. Lo tengo funcionando. Gracias..!
vigamage
18

Una manera elegante de esperar a que una función se complete primero es usar Promises con la función async / await .


  1. En primer lugar, crea una promesa . La función que creé se completará después de 2 segundos. Solía setTimeoutdemostrar la situación en la que las instrucciones tardarían un tiempo en ejecutarse.
  2. Para la segunda función, puede usar la función async / wait donde deberá completar awaitla 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
        let result = await firstFunction()
        console.log('promise resolved: ' + result)
        console.log('next step')
    }; 

    secondFunction()


Nota:

Usted podría simplemente resolveel Promisesin ningún valor como tal resolve(). En mi ejemplo, tengo resolvedel Promisevalor yque puedo usar en la segunda función.

Jakub A Suplicki
fuente
Sí, esto siempre funcionará. si alguien involucrado en este tipo de escenarios, entonces JavaScript Promise hará el truco por ellos.
Puttamarigowda MS
5

El único problema con las promesas es que IE no las admite. Edge sí, pero hay muchos IE 10 y 11: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise (compatibilidad en la parte inferior)

Entonces, JavaScript es de un solo subproceso. Si no está haciendo una llamada asincrónica, se comportará de manera predecible. El hilo principal de JavaScript ejecutará una función por completo antes de ejecutar la siguiente, en el orden en que aparecen en el código. Garantizar el orden de las funciones síncronas es trivial: cada función se ejecutará completamente en el orden en que se llamó.

Piense en la función síncrona como una unidad atómica de trabajo. . El hilo principal de JavaScript lo ejecutará completamente, en el orden en que aparecen las declaraciones en el código.

Pero, agregue la llamada asincrónica, como en la siguiente situación:

showLoadingDiv(); // function 1

makeAjaxCall(); // function 2 - contains async ajax call

hideLoadingDiv(); // function 3

Esto no hace lo que quieres . Ejecuta instantáneamente la función 1, la función 2 y la función 3. La carga de div parpadea y desaparece, mientras que la llamada ajax no está casi completa, aunque makeAjaxCall()haya regresado. LA COMPLICACIÓN es que makeAjaxCall()ha dividido su trabajo en trozos que avanzan poco a poco por cada giro del hilo principal de JavaScript : se está comportando de manera astronómica. Pero ese mismo hilo principal, durante una vuelta / ejecución, ejecutó las partes síncronas de forma rápida y previsible.

Entonces, la forma en que lo manejé : como dije, la función es la unidad atómica de trabajo. Combiné el código de la función 1 y 2: puse el código de la función 1 en la función 2, antes de la llamada asíncrona. Me deshice de la función 1. Todo, incluida la llamada asincrónica, se ejecuta de manera predecible, en orden.

ENTONCES, cuando la llamada asincrónica se completa, después de varios giros del hilo principal de JavaScript, haga que llame a la función 3. Esto garantiza el orden . Por ejemplo, con ajax, el controlador de eventos onreadystatechange se llama varias veces. Cuando informa que se ha completado, llame a la función final que desee.

Estoy de acuerdo que es más desordenado. Me gusta que el código sea simétrico, me gusta que las funciones hagan una cosa (o casi), y no me gusta que la llamada ajax sea responsable de la pantalla (creando una dependencia en la persona que llama). PERO, con una llamada asincrónica incrustada en una función síncrona, se deben hacer compromisos para garantizar el orden de ejecución. Y tengo que codificar para IE 10, así que no hay promesas.

Resumen : para llamadas síncronas, garantizar el orden es trivial. Cada función se ejecuta completamente en el orden en que se llamó. Para una función con una llamada asincrónica, la única forma de garantizar el orden es monitorear cuando se completa la llamada asincrónica y llamar a la tercera función cuando se detecta ese estado.

Para una discusión de los hilos de JavaScript, consulte: https://medium.com/@francesco_rizzi/javascript-main-thread-dissected-43c85fce7e23 y https://developer.mozilla.org/en-US/docs/Web/JavaScript/ EventLoop

Además, otra pregunta similar y altamente calificada sobre este tema: ¿Cómo debo llamar a 3 funciones para ejecutarlas una tras otra?

Kermit
fuente
8
Para el votante (s), me gustaría saber qué le pasa a esta respuesta.
kermit
0

Esto es lo que se me ocurrió, ya que necesito ejecutar varias operaciones en una cadena.

<button onclick="tprom('Hello Niclas')">test promise</button>

<script>
    function tprom(mess) {
        console.clear();

        var promise = new Promise(function (resolve, reject) {
            setTimeout(function () {
                resolve(mess);
            }, 2000);
        });

        var promise2 = new Promise(async function (resolve, reject) {
            await promise;
            setTimeout(function () {
                resolve(mess + ' ' + mess);
            }, 2000);
        });

        var promise3 = new Promise(async function (resolve, reject) {
            await promise2;
            setTimeout(function () {
                resolve(mess + ' ' + mess+ ' ' + mess);
            }, 2000);
        });

        promise.then(function (data) {
            console.log(data);
        });

        promise2.then(function (data) {
            console.log(data);
        });

        promise3.then(function (data) {
            console.log(data);
        });
    }

</script>
Niclas Arnberger
fuente
Agregue al menos solo comentarios de alto nivel para la descripción del fragmento dado. Esto es para explicar cómo su código resuelve el problema.
Cold Cerberus
El problema era ejecutar una segunda función después de la primera. Muestro cómo ejecutar una segunda función después de la primera, y también cómo ejecutar una tercera función después de la segunda. Hice tres funciones para facilitar la comprensión del código que se requiere.
Niclas Arnberger el
0

Su diversión principal llamará primeroDiversión y luego, al completarla, llamará su próxima diversión.

async firstFunction() {
            const promise = new Promise((resolve, reject) => {
                for (let i = 0; i < 5; i++) {
                    // do something
                    console.log(i);
                    if (i == 4) {
                        resolve(i);
                    }
                }
            });
            const result = await promise;
        }

        second() {
            this.firstFunction().then( res => {
                // third function call do something
                console.log('Gajender here');
            });
        }
Gajender Singh
fuente