¿Cómo ejecutar una función de Javascript solo después de que se hayan completado muchas otras funciones?

Mi problema específico es que necesito ejecutar una (potencialmente) gran cantidad de funciones de Javascript para preparar algo así como un archivo por lotes (cada llamada de función agrega información al mismo archivo por lotes) y luego, después de que se completen todas esas llamadas, ejecutar un función final para enviar el archivo por lotes (digamos, enviarlo como una respuesta HTML). Estoy buscando un patrón general de programación de Javascript para esto.

Problema generalizado: dadas las funciones de Javascript funcA(), funcB() y funcC(), me gustaría encontrar la mejor manera de ordenar la ejecución para que funcC solo se ejecute después de que funcA y funcB se hayan ejecutado. Sé que podría usar funciones de devolución de llamada anidadas como esta:

funcA = function() {
    //Does funcA stuff
    funcB();
}
funcB = function() {
    //Does funcB stuff
    funcC();
}

funcA();

Incluso podría hacer que este patrón sea un poco más general al pasar parámetros de devolución de llamada, sin embargo, esta solución se vuelve bastante detallada.

También estoy familiarizado con el encadenamiento de funciones de Javascript donde una solución podría verse así:

myObj = {}
myObj.answer = ""
myObj.funcA = function() {
    //Do some work on this.answer
    return this;
}
myObj.funcB = function() {
    //Do some more work on this.answer
    return this;
}
myObj.funcC = function() {
    //Use the value of this.answer now that funcA and funcB have made their modifications
    return this;
}
myObj.funcA().funcB().funcC();

Si bien esta solución me parece un poco más limpia, a medida que agrega más pasos al cálculo, la cadena de ejecución de funciones se hace más y más larga.

Para mi problema específico, el orden en que se ejecutan funcA, funcB, etc. NO importa. Entonces, en mis soluciones anteriores, técnicamente estoy haciendo más trabajo del necesario porque estoy colocando todas las funciones en un orden en serie. Todo lo que me importa es que funcC (una función para enviar el resultado o activar una solicitud) solo se llama después de que funcA y funcB hayan completado TODA la ejecución. ¿Idealmente, funcC podría de alguna manera escuchar todas las llamadas de funciones intermedias para completar y ENTONCES se ejecutaría? Espero aprender un patrón general de Javascript para resolver ese problema.

Gracias por tu ayuda.

Otra idea: ¿Quizás pasar un objeto compartido a funcA y funcB y cuando completen la ejecución marque el objeto compartido como sharedThing.funcA = "complete" o sharedThing.funcB = "complete" y luego de alguna manera? haga que la función se ejecute cuando el objeto compartido alcance un estado en el que todos los campos estén marcados como completos. No estoy seguro de cómo exactamente podrías hacer que FuncC espere esto.

Editar: debo tener en cuenta que estoy usando Javascript del lado del servidor (Node.js) y me gustaría aprender un patrón para resolverlo simplemente usando Javascript antiguo (sin el uso de jQuery u otras bibliotecas). ¿Seguramente este problema es lo suficientemente general como para que haya una solución limpia y pura de Javascript?

preguntado el 30 de junio de 12 a las 23:06

Cree una matriz de referencias a sus funciones en el orden en que desea ejecutarlas y luego pase la matriz a otra función que simplemente repita las funciones de llamada de la matriz. Asegúrate de que el que quieres que sea el último sea el último. -

O simplemente escriba el código en el orden en que desea que se ejecute; no me queda claro cual es el problema realmente es. La codificación imperativa secuencial siempre lo hace comprometerse con un orden de ejecución de las declaraciones del programa; ¿Qué tiene de especial esta situación? -

eso es bastante inteligente Puntiagudo, nunca pensé en hacer eso. ¿Qué pasa si su función tiene un retraso para comenzar? ¿Todavía esperará a estar terminado? -

Puede ser exagerado, pero jQuery tiene una función diferida. Básicamente te permite hacer un $.when(A, B).then(C) como sintaxis. -

Como dijo Pointy, ¿cuál es el verdadero problema aquí? ¿Alguna de estas funciones es asíncrona? Si no, entonces javascript simplemente ejecuta las cosas secuencialmente como las ha codificado. -

7 Respuestas

Si desea mantenerlo simple, puede usar un sistema de devolución de llamada basado en contador. He aquí un borrador de un sistema que permite when(A, B).then(C) sintaxis. (when/then en realidad es solo azúcar, pero, de nuevo, podría decirse que todo el sistema lo es).

var when = function() {
  var args = arguments;  // the functions to execute first
  return {
    then: function(done) {
      var counter = 0;
      for(var i = 0; i < args.length; i++) {
        // call each function with a function to call on done
        args[i](function() {
          counter++;
          if(counter === args.length) {  // all functions have notified they're done
            done();
          }
        });
      }
    }
  };
};

Uso:

when(
  function(done) {
    // do things
    done();
  },
  function(done) {
    // do things
    setTimeout(done, 1000);
  },
  ...
).then(function() {
  // all are done
});

Respondido el 30 de junio de 12 a las 23:06

Si no usa ninguna función asincrónica y su secuencia de comandos no rompe el orden de ejecución, entonces la solución más simple es, como lo indican Pointy y otros:

funcA(); 
funcB();
funcC();

Sin embargo, dado que está usando node.js, creo que va a usar funciones asíncronas y desea ejecutar funcC después de que haya finalizado una solicitud de E/S asíncrona, por lo que debe usar algún tipo de mecanismo de conteo, por ejemplo:

var call_after_completion = function(callback){
    this._callback = callback;
    this._args = [].slice.call(arguments,1);
    this._queue = {};
    this._count = 0;
    this._run = false;
}

call_after_completion.prototype.add_condition = function(str){
    if(this._queue[str] !== undefined)
        throw new TypeError("Identifier '"+str+"' used twice");
    else if(typeof str !== "String" && str.toString === undefined)
        throw new TypeError("Identifier has to be a string or needs a toString method");

    this._queue[str] = 1;
    this._count++;
    return str;
}

call_after_completion.prototype.remove_condition = function(str){
    if(this._queue[str] === undefined){
        console.log("Removal of condition '"+str+"' has no effect");
        return;
    }
    else if(typeof str !== "String" && str.toString === undefined)
        throw new TypeError("Identifier has to be a string or needs a toString method");

    delete this._queue[str];

    if(--this._count === 0 && this._run === false){
        this._run = true;
        this._callback.apply(null,this._args);
    }
}

Puede simplificar este objeto ignorando el identificador str y solo aumentando/disminuyendo this._count, sin embargo, este sistema podría ser útil para la depuración.

Para usar call_after_completion simplemente crea un new call_after_completion con su función deseada func como argumento y add_conditions. func solo se llamará si se han eliminado todas las condiciones.

Ejemplo:

var foo = function(){console.log("foo");}
var bar = new call_after_completion(foo);
var i;

bar.add_condition("foo:3-Second-Timer");
bar.add_condition("foo:additional function");
bar.add_condition("foo:for-loop-finished");

function additional_stuff(cond){
    console.log("additional things");
    cond.remove_condition("foo:additional function");
}

for(i = 0; i < 1000; ++i){

}
console.log("for loop finished");
bar.remove_condition("foo:for-loop-finished");
additional_stuff(bar);

setTimeout(function(){
    console.log("3 second timeout");
    bar.remove_condition("foo:3-Second-Timer");
},3000);

Demostración de JSFiddle

Respondido el 30 de junio de 12 a las 23:06

Si no desea usar ninguna biblioteca de ayuda, entonces necesita escribir alguna ayuda usted mismo, no hay una solución simple de una línea para esto.

Si desea terminar con algo que se vea tan legible como lo sería en caso síncrono, intente alguna implementación de concepto diferido/prometido (todavía es JavaScript simple), por ejemplo, usando deferred paquete puede terminar con algo tan simple como:

// Invoke one after another:
funcA()(funcB)(funcC);

// Invoke funcA and funcB simultaneously and afterwards funcC:
funcA()(funcB())(funcC);

// If want result of both funcA and funcB to be passed to funcC:
deferred(funcA(), funcB())(funcC);

Respondido 12 Jul 12, 20:07

Eche un vistazo a los objetos diferidos de jQuery. Esto proporciona un medio sofisticado para controlar lo que sucede en un entorno asíncrono.

El caso de uso obvio para esto es AJAX, pero no se limita a esto.

Recursos:

Respondido el 30 de junio de 12 a las 23:06

Esto suena interesante, pero 2 de los 3 enlaces que proporcionas están rotos. - crantok

Estaba buscando el mismo tipo de patrón. Estoy usando API que interrogan múltiples fuentes de datos remotas. Cada API requiere que les pase una función de devolución de llamada. Esto significa que no puedo simplemente activar un conjunto de mis propias funciones y esperar a que regresen. En su lugar, necesito una solución que funcione con un conjunto de devoluciones de llamada que se pueden llamar en cualquier orden según la capacidad de respuesta de las diferentes fuentes de datos.

Se me ocurrió la siguiente solución. JS está muy por debajo de la lista de idiomas con los que estoy más familiarizado, por lo que puede que no sea un modismo muy JS.

function getCallbackCreator( number_of_data_callbacks, final_callback ) {

    var all_data = {}

    return function ( data_key ) {

        return function( data_value ) {
            all_data[data_key] = data_value;

            if ( Object.keys(all_data).length == number_of_data_callbacks ) {
                final_callback( all_data );
            }
        }
    }
}

var getCallback = getCallbackCreator( 2, inflatePage );

myGoogleDataFetcher( getCallback( 'google' ) );
myCartoDataFetcher( getCallback( 'cartodb' ) );

Editar: la pregunta se etiquetó con node.js pero el OP dijo: "Estoy buscando un patrón de programación Javascript general para esto", así que publiqué esto aunque no estoy usando node.

Respondido el 29 de Septiembre de 15 a las 16:09

Hoy en día, uno puede hacer algo como esto:

Digamos que tenemos funcA, funcB y funcC:

Si uno quiere que los resultados de funcA y funcB se pasen a funcC:

var promiseA = new Promise((resolve, reject) => {
  resolve(await funcA());
});
var promiseB = new Promise((resolve, reject) => {
  resolve(await funcB());
});
var promise = Promise.all([ promiseA, promiseB ]).then(results => {
  // results = [result from funcA, result from funcB]
  return funcC(results);
});

Si uno quiere funcA, entonces funcB y luego funcC:

var promise = (
  new Promise(async resolve => resolve( await funcA() ))
).then(result_a => funcB(result_a)).then(result_b => funcC(result_b));

Y por último:

promise.then(result_c => console.log('done.'));

respondido 05 nov., 20:15

how about:
funcC(funcB(funcA)));

Creo que la pregunta se debe a que algunas funciones se ejecutan durante más tiempo y puede haber una situación en la que ejecutamos funcC cuando funcA o funcB no terminaron de ejecutarse.

Respondido 24 Oct 17, 05:10

En @Aleksander Kogut, responda una visión más clara, pero desde su enfoque, puede ser como: resultado = funcA (); resultado = funcB(resultado); funcC(resultado); porque me pasaría la cadena de resultados. - Vasyl Lyashkévych

acaba de corregir el código anterior. Para mí ahora funciona perfecto. Incluso uso más funciones anidadas. - Alaska

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.