¿Cómo debo llamar a 3 funciones para ejecutarlas una tras otra?


151

Si necesito llamar a estas funciones una tras otra,

$('#art1').animate({'width':'1000px'},1000);        
$('#art2').animate({'width':'1000px'},1000);        
$('#art3').animate({'width':'1000px'},1000);        

Sé que en jQuery podría hacer algo como:

$('#art1').animate({'width':'1000px'},1000,'linear',function(){
    $('#art2').animate({'width':'1000px'},1000,'linear',function(){
        $('#art3').animate({'width':'1000px'},1000);        
    });        
});        

Pero, supongamos que no estoy usando jQuery y quiero llamar:

some_3secs_function(some_value);        
some_5secs_function(some_value);        
some_8secs_function(some_value);        

¿Cómo debo llamar a estas funciones para ejecutar some_3secs_function, y DESPUÉS de que finalice la llamada, luego ejecutar some_5secs_functiony DESPUÉS de que finalice esa llamada, luego llamar some_8secs_function?

ACTUALIZAR:

Esto todavía no funciona:

(function(callback){
    $('#art1').animate({'width':'1000px'},1000);
    callback();
})((function(callback2){
    $('#art2').animate({'width':'1000px'},1000);
    callback2();
})(function(){
    $('#art3').animate({'width':'1000px'},1000);
}));

Tres animaciones comienzan al mismo tiempo

¿Dónde está mi error?


¿Quieres decir que las funciones se invocan exactamente en 3 5 y 8 segundos o solo una tras otra?
Trass Vasston

Yo creo que simplemente está seguro acerca vs sincrónica ejecución de la función asíncrona. He actualizado mi respuesta a continuación. Espero eso ayude.
Wayne

Respuestas:


243

En Javascript, hay funciones sincrónicas y asincrónicas .

Funciones sincrónicas

La mayoría de las funciones en Javascript son sincrónicas. Si tuviera que llamar a varias funciones síncronas seguidas

doSomething();
doSomethingElse();
doSomethingUsefulThisTime();

ellos ejecutarán en orden. doSomethingElseno comenzará hasta que se doSomethinghaya completado. doSomethingUsefulThisTime, a su vez, no comenzará hasta que se doSomethingElsehaya completado.

Funciones asincrónicas

Sin embargo, la función asincrónica no se esperará la una a la otra. Veamos el mismo ejemplo de código que teníamos arriba, esta vez suponiendo que las funciones son asíncronas.

doSomething();
doSomethingElse();
doSomethingUsefulThisTime();

Las funciones se inicializarán en orden, pero todas se ejecutarán aproximadamente al mismo tiempo. No puede predecir constantemente cuál terminará primero: el que tarde menos tiempo en ejecutarse terminará primero.

Pero a veces, desea que las funciones que sean asíncronas se ejecuten en orden, y a veces desea que las funciones que sean sincrónicas se ejecuten de forma asíncrona. Afortunadamente, esto es posible con devoluciones de llamada y tiempos de espera, respectivamente.

Devoluciones de llamada

Supongamos que tenemos tres funciones asíncronas que queremos ejecutar en orden, some_3secs_function, some_5secs_function, y some_8secs_function.

Dado que las funciones se pueden pasar como argumentos en Javascript, puede pasar una función como devolución de llamada para que se ejecute después de que la función se haya completado.

Si creamos las funciones como esta

function some_3secs_function(value, callback){
  //do stuff
  callback();
}

entonces puedes llamar en orden, así:

some_3secs_function(some_value, function() {
  some_5secs_function(other_value, function() {
    some_8secs_function(third_value, function() {
      //All three functions have completed, in order.
    });
  });
});

Tiempos de espera

En Javascript, puede indicarle a una función que se ejecute después de un cierto tiempo de espera (en milisegundos). De hecho, esto puede hacer que las funciones síncronas se comporten de forma asíncrona.

Si tenemos tres funciones sincrónicas, podemos ejecutarlas de forma asincrónica utilizando la setTimeoutfunción.

setTimeout(doSomething, 10);
setTimeout(doSomethingElse, 10);
setTimeout(doSomethingUsefulThisTime, 10);

Sin embargo, esto es un poco feo y viola el principio DRY [wikipedia] . Podríamos limpiar esto un poco creando una función que acepte un conjunto de funciones y un tiempo de espera.

function executeAsynchronously(functions, timeout) {
  for(var i = 0; i < functions.length; i++) {
    setTimeout(functions[i], timeout);
  }
}

Esto se puede llamar así:

executeAsynchronously(
    [doSomething, doSomethingElse, doSomethingUsefulThisTime], 10);

En resumen, si tiene funciones asincrónicas que desea ejecutar sincrónicamente, use devoluciones de llamada, y si tiene funciones sincrónicas que desea ejecutar de forma asincrónica, use tiempos de espera.


77
Esto no retrasará las funciones durante 3,5 y 8 segundos, como sugiere el ejemplo, simplemente se ejecutarán una tras otra.
Trass Vasston

1
@ Peter - Espera, así que estoy confundido. Si se trata de llamadas sincrónicas simples que tardan unos segundos en completarse, ¿por qué necesitamos algo de esto?
Wayne

9
@Peter - +1 para el método más bello y complicado que he visto para llamar a tres funciones síncronas en secuencia.
Wayne

44
Gracias por explicar expertamente la diferencia entre las funciones async y sync js. Esto explica mucho.
jnelson

2
Esto NO es correcto por las siguientes razones: (1) los 3 tiempos de espera se resolverán después de 10 segundos, por lo que las 3 líneas se disparan al mismo tiempo. (2) este método requiere que conozca la duración por adelantado y que las funciones de "programación" sucedan en el futuro, en lugar de esperar a que se resuelvan las funciones asíncronas anteriores en la cadena y que ese sea el desencadenante. --- en su lugar, desea utilizar una de las siguientes respuestas utilizando devoluciones de llamada, promesas o la biblioteca asíncrona.
zeroasterisk

37

Esta respuesta utiliza promises, una característica de JavaScript del ECMAScript 6estándar. Si su plataforma de destino no es compatible promises, complétela con PromiseJs .

Mire mi respuesta aquí Espere hasta que una función con animaciones termine hasta ejecutar otra función si desea usar jQueryanimaciones.

Así es como se vería su código con ES6 Promisesy jQuery animations.

Promise.resolve($('#art1').animate({ 'width': '1000px' }, 1000).promise()).then(function(){
    return Promise.resolve($('#art2').animate({ 'width': '1000px' }, 1000).promise());
}).then(function(){
    return Promise.resolve($('#art3').animate({ 'width': '1000px' }, 1000).promise());
});

Los métodos normales también se pueden envolver Promises.

new Promise(function(fulfill, reject){
    //do something for 5 seconds
    fulfill(result);
}).then(function(result){
    return new Promise(function(fulfill, reject){
        //do something for 5 seconds
        fulfill(result);
    });
}).then(function(result){
    return new Promise(function(fulfill, reject){
        //do something for 8 seconds
        fulfill(result);
    });
}).then(function(result){
    //do something with the result
});

El thenmétodo se ejecuta tan pronto como Promisetermine. Normalmente, el valor de retorno del functionpasado thense pasa al siguiente como resultado.

Pero si Promisese devuelve a, la siguiente thenfunción espera hasta que Promisefinalice la ejecución y recibe los resultados (el valor al que se pasa fulfill).


Sé que esto es útil, pero encontré que el código proporcionado es difícil de entender sin buscar un ejemplo del mundo real para darle algo de contexto. Encontré este video en YouTube: youtube.com/watch?v=y5mltEaQxa0 - y escribí la fuente del video aquí drive.google.com/file/d/1NrsAYs1oaxXw0kv9hz7a6LjtOEb6x7z-/... Hay algunos matices más como la captura que falta en este ejemplo que esto elabora. (use una identificación diferente en la línea getPostById () o intente cambiar el nombre de un autor para que no coincida con una publicación, etc.)
JGFMK

20

Parece que no estás apreciando completamente la diferencia entre síncrono y asíncrono ejecución de funciones .

El código que proporcionó en su actualización ejecuta inmediatamente cada una de sus funciones de devolución de llamada, que a su vez inicia inmediatamente una animación. Las animaciones, sin embargo, se ejecutan de forma asíncrona . Funciona así:

  1. Realizar un paso en la animación.
  2. Llamada setTimeoutcon una función que contiene el siguiente paso de animación y un retraso
  3. Pasa el tiempo
  4. La devolución de llamada dada a setTimeoutejecuta
  5. Regrese al paso 1

Esto continúa hasta que se complete el último paso en la animación. Mientras tanto, sus funciones síncronas se han completado hace mucho tiempo. En otras palabras, su llamada a la animatefunción realmente no toma 3 segundos. El efecto se simula con retrasos y devoluciones de llamada.

Lo que necesitas es una cola . Internamente, jQuery pone en cola las animaciones, solo ejecutando su devolución de llamada una vez que se completa su animación correspondiente. Si su devolución de llamada inicia otra animación, el efecto es que se ejecutan en secuencia.

En el caso más simple, esto es equivalente a lo siguiente:

window.setTimeout(function() {
    alert("!");
    // set another timeout once the first completes
    window.setTimeout(function() {
        alert("!!");
    }, 1000);
}, 3000); // longer, but first

Aquí hay una función general de bucle asíncrono. Llamará a las funciones dadas en orden, esperando el número especificado de segundos entre cada una.

function loop() {
    var args = arguments;
    if (args.length <= 0)
        return;
    (function chain(i) {
        if (i >= args.length || typeof args[i] !== 'function')
            return;
        window.setTimeout(function() {
            args[i]();
            chain(i + 1);
        }, 2000);
    })(0);
}    

Uso:

loop(
  function() { alert("sam"); }, 
  function() { alert("sue"); });

Obviamente, podría modificar esto para tomar tiempos de espera configurables o ejecutar inmediatamente la primera función o detener la ejecución cuando una función en la cadena regrese falseo a applylas funciones en un contexto específico o cualquier otra cosa que pueda necesitar.


14

Creo que la biblioteca asíncrona le proporcionará una forma muy elegante de hacer esto. Si bien las promesas y las devoluciones de llamada pueden ser un poco difíciles de hacer malabares, la sincronización puede proporcionar patrones claros para simplificar su proceso de pensamiento. Para ejecutar funciones en serie, deberá colocarlas en una cascada asíncrona . En la jerga asíncrona, cada función se llama a taskque toma algunos argumentos y a callback; cual es la siguiente función en la secuencia. La estructura básica se vería así:

async.waterfall([
  // A list of functions
  function(callback){
      // Function no. 1 in sequence
      callback(null, arg);
  },
  function(arg, callback){
      // Function no. 2 in sequence
      callback(null);
  }
],    
function(err, results){
   // Optional final callback will get results for all prior functions
});

Acabo de intentar explicar brevemente la estructura aquí. Lea la guía de la cascada para obtener más información, está bastante bien escrita.


1
Esto realmente hace que JS sea un poco más soportable.
Mike

9

sus funciones deben tomar una función de devolución de llamada, que se llama cuando finaliza.

function fone(callback){
...do something...
callback.apply(this,[]);

}

function ftwo(callback){
...do something...
callback.apply(this,[]);
}

entonces el uso sería como:

fone(function(){
  ftwo(function(){
   ..ftwo done...
  })
});

4
asec=1000; 

setTimeout('some_3secs_function("somevalue")',asec*3);
setTimeout('some_5secs_function("somevalue")',asec*5);
setTimeout('some_8secs_function("somevalue")',asec*8);

No entraré en una discusión profunda sobre setTimeout aquí, pero:

  • en este caso agregué el código para ejecutar como una cadena. Esta es la forma más sencilla de pasar un var a su función setTimeout-ed, pero los puristas se quejarán.
  • También puede pasar un nombre de función sin comillas, pero no se puede pasar ninguna variable.
  • su código no espera a que se active setTimeout.
  • Esto puede ser difícil de entender al principio: debido al punto anterior, si pasa una variable de su función de llamada, esa variable ya no existirá cuando se active el tiempo de espera: la función de llamada se habrá ejecutado y es Vars desaparecido.
  • Se sabe que uso funciones anónimas para solucionar todo esto, pero bien podría haber una mejor manera,

3

Como lo etiquetó con javascript, usaría un control de temporizador ya que los nombres de sus funciones son 3, 5 y 8 segundos. Por lo tanto, inicie su temporizador, 3 segundos, llame al primero, 5 segundos al segundo, 8 segundos al tercero, y cuando termine, detenga el temporizador.

Normalmente, en Javascript, lo que tiene es correcto para que las funciones se ejecuten una tras otra, pero como parece que está tratando de hacer una animación programada, un temporizador sería su mejor opción.


2

//sample01
(function(_){_[0]()})([
	function(){$('#art1').animate({'width':'10px'},100,this[1].bind(this))},
	function(){$('#art2').animate({'width':'10px'},100,this[2].bind(this))},
	function(){$('#art3').animate({'width':'10px'},100)},
])

//sample02
(function(_){_.next=function(){_[++_.i].apply(_,arguments)},_[_.i=0]()})([
	function(){$('#art1').animate({'width':'10px'},100,this.next)},
	function(){$('#art2').animate({'width':'10px'},100,this.next)},
	function(){$('#art3').animate({'width':'10px'},100)},
]);

//sample03
(function(_){_.next=function(){return _[++_.i].bind(_)},_[_.i=0]()})([
	function(){$('#art1').animate({'width':'10px'},100,this.next())},
	function(){$('#art2').animate({'width':'10px'},100,this.next())},
	function(){$('#art3').animate({'width':'10px'},100)},
]);


¿Puedes explicar qué es esto? ¿Qué está ligado al guión bajo? ¿Qué hace la función asignada next?
mtso

1
Explico la muestra 2 con el uso de jsfiddle. jsfiddle.net/mzsteyuy/3 Si me permite explicarlo más o menos, el ejemplo 2 es una forma corta del código en jsfiddle. el guión bajo es Array cuyos elementos son contravalorables (i) y funcionan a continuación y funcionan [0] ~ [2].
Yuuya

1

También podría usar promesas de esta manera:

    some_3secs_function(this.some_value).then(function(){
       some_5secs_function(this.some_other_value).then(function(){
          some_8secs_function(this.some_other_other_value);
       });
    });

Tendrías que hacer some_value global para acceder desde el interior de .then

Alternativamente, desde la función externa, podría devolver el valor que usaría la función interna, así:

    one(some_value).then(function(return_of_one){
       two(return_of_one).then(function(return_of_two){
          three(return_of_two);
       });
    });

0

Uso una función 'waitUntil' basada en setTimeout de javascript

/*
    funcCond : function to call to check whether a condition is true
    readyAction : function to call when the condition was true
    checkInterval : interval to poll <optional>
    timeout : timeout until the setTimeout should stop polling (not 100% accurate. It was accurate enough for my code, but if you need exact milliseconds, please refrain from using Date <optional>
    timeoutfunc : function to call on timeout <optional>
*/
function waitUntil(funcCond, readyAction, checkInterval, timeout, timeoutfunc) {
    if (checkInterval == null) {
        checkInterval = 100; // checkinterval of 100ms by default
    }
    var start = +new Date(); // use the + to convert it to a number immediatly
    if (timeout == null) {
        timeout = Number.POSITIVE_INFINITY; // no timeout by default
    }
    var checkFunc = function() {
        var end = +new Date(); // rough timeout estimations by default

        if (end-start > timeout) {
            if (timeoutfunc){ // if timeout function was defined
                timeoutfunc(); // call timeout function
            }
        } else {
            if(funcCond()) { // if condition was met
                readyAction(); // perform ready action function
            } else {
                setTimeout(checkFunc, checkInterval); // else re-iterate
            }
        }
    };
    checkFunc(); // start check function initially
};

Esto funcionaría perfectamente si sus funciones establecen una cierta condición en verdadera, que podría sondear. Además, viene con tiempos de espera, que le ofrecen alternativas en caso de que su función no haya podido hacer algo (incluso dentro del rango de tiempo. ¡Piense en los comentarios de los usuarios!)

p.ej

doSomething();
waitUntil(function() { return doSomething_value===1;}, doSomethingElse);
waitUntil(function() { return doSomethingElse_value===1;}, doSomethingUseful);

Notas

La fecha causa estimaciones aproximadas del tiempo de espera. Para mayor precisión, cambie a funciones como console.time (). Tenga en cuenta que Date ofrece una mayor compatibilidad entre navegadores y heredados. Si no necesita mediciones exactas en milisegundos; no se moleste o, alternativamente, lo envuelva y ofrezca console.time () cuando el navegador lo admita


0

Si el método 1 tiene que ejecutarse después de los métodos 2, 3, 4. El siguiente fragmento de código puede ser la solución para esto usando el objeto diferido en JavaScript.

function method1(){
  var dfd = new $.Deferred();
     setTimeout(function(){
     console.log("Inside Method - 1"); 
     method2(dfd);	 
    }, 5000);
  return dfd.promise();
}

function method2(dfd){
  setTimeout(function(){
   console.log("Inside Method - 2"); 
   method3(dfd);	
  }, 3000);
}

function method3(dfd){
  setTimeout(function(){
   console.log("Inside Method - 3"); 	
   dfd.resolve();
  }, 3000);
}

function method4(){   
   console.log("Inside Method - 4"); 	
}

var call = method1();

$.when(call).then(function(cb){
  method4();
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.