web-dev-qa-db-fra.com

Comment exécuter une fonction Javascript seulement après que plusieurs autres fonctions ont été complétées?

Mon problème spécifique est que je dois exécuter un grand nombre (potentiellement) de fonctions Javascript pour préparer quelque chose comme un fichier batch (chaque appel de fonction ajoute des informations au même fichier batch), puis, une fois tous ces appels terminés, exécuter un fonction finale pour envoyer le fichier de commandes (par exemple, envoyez-le sous forme de réponse HTML). Je cherche un modèle de programmation Javascript général pour cela.

Problème général: Étant donné les fonctions Javascript funcA (), funcB () et funcC (), je voudrais trouver le meilleur moyen d'exécuter l'ordre, de sorte que funcC ne soit exécuté qu'après l'exécution de funcA et de funcB. Je sais que je pourrais utiliser des fonctions de rappel imbriquées comme ceci:

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

funcA();

Je pourrais même rendre ce modèle un peu plus général en passant des paramètres de rappel, cependant, cette solution devient assez détaillée.

Je connais aussi très bien le chaînage de fonctions Javascript où une solution pourrait ressembler à ceci:

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();

Bien que cette solution me semble un peu plus propre, à mesure que vous ajoutez des étapes au calcul, la chaîne d'exécutions de fonctions s'allonge de plus en plus.

Pour mon problème spécifique, l'ordre dans lequel les fonctions funcA, funcB, etc. sont exécutées importe peu. Donc, dans les solutions ci-dessus, je fais techniquement plus de travail que nécessaire, car je place toutes les fonctions dans une commande en série. Ce qui compte pour moi, c’est que funcC (une fonction permettant d’envoyer le résultat ou de lancer une requête) n’est appelée que lorsque funcA et funcB ont TOUT été exécutées. Idéalement, funcC pourrait en quelque sorte écouter que tous les appels de fonction intermédiaires soient terminés et ALORS s’exécuter? J'espère apprendre un modèle Javascript général pour résoudre un tel problème.

Merci de votre aide.

Une autre idée: peut-être transmettre un objet partagé à funcA et funcB et, une fois leur exécution terminée, marquer l'objet partagé comme sharedThing.funcA = "complete" ou sharedThing.funcB = "complete", puis d'une manière ou d'une autre? as funcC à exécuter lorsque l'objet partagé atteint un état dans lequel tous les champs sont marqués comme étant terminés. Je ne sais pas comment vous pourriez faire attendre exactement cela.

Edit: Je dois noter que j'utilise le Javascript côté serveur (Node.js) et j'aimerais apprendre un motif pour le résoudre en utilisant simplement du vieux Javascript (sans utiliser jQuery ou d'autres bibliothèques). Ce problème est sûrement assez général pour qu’il existe une solution pure en Javascript.

18
dgh

Si vous voulez garder les choses simples, vous pouvez utiliser un système de rappel basé sur des compteurs. Voici un brouillon d'un système qui autorise la syntaxe when(A, B).then(C). (when/then est en fait juste du sucre, mais encore une fois, tout le système l'est sans doute.)

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();
          }
        });
      }
    }
  };
};

Usage:

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

Si vous n'utilisez aucune fonction asynchrone et que votre script ne casse pas l'ordre d'exécution, la solution la plus simple est, comme l'indiquent Pointy et d'autres:

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

Cependant, puisque vous utilisez node.js, je pense que vous allez utiliser des fonctions asynchrones et que vous voulez exécuter funcC après qu'une requête async IO est terminée. Vous devez donc utiliser des mécanismes de comptage, par exemple. Exemple:

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);
    }
}

Vous pouvez simplifier cet objet en ignorant l'identifiant str et en augmentant/diminuant simplement this._count; toutefois, ce système pourrait être utile pour le débogage.

Pour utiliser call_after_completion, vous créez simplement un new call_after_completion avec votre fonction désirée func en argument et add_conditions. func ne sera appelé que si toutes les conditions ont été supprimées.

Exemple:

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);

JSFiddle Demo

1
Zeta

Si vous ne souhaitez utiliser aucune bibliothèque d'assistance, vous devez écrire vous-même une aide, il n'existe pas de solution simple à une ligne pour cela.

Si vous souhaitez terminer avec quelque chose qui semble aussi lisible que dans les cas synchrones, essayez une implémentation de concept différé/de promesse (c'est toujours du JavaScript pur), par exemple. En utilisant le paquet deferred, vous pourriez vous retrouver avec quelque chose d'aussi simple que:

// 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);
1
Mariusz Nowak

Je cherchais le même genre de motif. J'utilise des API qui interrogent plusieurs sources de données distantes. Les API nécessitent chacune que je leur transmette une fonction de rappel. Cela signifie que je ne peux pas simplement lancer un ensemble de mes propres fonctions et attendre leur retour. J'ai plutôt besoin d'une solution qui fonctionne avec un ensemble de rappels pouvant être appelés dans n'importe quel ordre en fonction de la réactivité des différentes sources de données.

Je suis venu avec la solution suivante. JS fait partie de la liste des langues que je connais le mieux. Ce n’est peut-être pas un idiome très 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' ) );

Edit: La question était étiquetée avec node.js mais le PO a déclaré: "Je recherche un modèle de programmation Javascript général pour cela", donc j’ai posté ceci même si je n’utilise pas node.

0
crantok

Consultez les objets différés de jQuery. Cela fournit un moyen sophistiqué de contrôler ce qui se passe dans un environnement asynchrone.

Le cas d'utilisation évident pour cela est AJAX, mais cela ne se limite pas à cela.

Ressources:

0
Utkanos