Comment appeler 3 fonctions pour les exécuter l'une après l'autre?


151

Si j'ai besoin d'appeler ces fonctions l'une après l'autre,

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

Je sais que dans jQuery je pourrais faire quelque chose comme:

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

Mais, supposons que je n'utilise pas jQuery et que je souhaite appeler:

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

Comment dois-je appeler ces fonctions pour exécuter some_3secs_function, et APRÈS cet appel se termine, puis exécuter some_5secs_functionet APRÈS cet appel se termine, puis appeler some_8secs_function?

METTRE À JOUR:

Cela ne fonctionne toujours pas:

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

Trois animations démarrent en même temps

Où est mon erreur?


voulez-vous dire que les fonctions doivent être appelées en exactement 3 5 et 8 secondes ou juste l'une après l'autre?
Trass Vasston

Je pense que vous n'êtes tout simplement pas sûr de l'exécution des fonctions synchrones et asynchrones. J'ai mis à jour ma réponse ci-dessous. J'espère que ça aide.
Wayne

Réponses:


243

En Javascript, il existe des fonctions synchrones et asynchrones .

Fonctions synchrones

La plupart des fonctions en Javascript sont synchrones. Si vous appelez plusieurs fonctions synchrones d'affilée

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

ils exécuteront dans l'ordre. doSomethingElsene démarrera pas tant qu'il ne sera pas doSomethingterminé. doSomethingUsefulThisTime, à son tour, ne démarrera pas tant qu'il ne sera pas doSomethingElseterminé.

Fonctions asynchrones

La fonction asynchrone, cependant, n'attendra pas l'une l'autre. Regardons le même exemple de code que nous avions ci-dessus, en supposant cette fois que les fonctions sont asynchrones

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

Les fonctions seront initialisées dans l'ordre, mais elles s'exécuteront toutes à peu près en même temps. Vous ne pouvez pas toujours prédire lequel se terminera en premier: celui qui prendra le moins de temps à s'exécuter finira en premier.

Mais parfois, vous voulez que les fonctions asynchrones s'exécutent dans l'ordre, et parfois vous voulez que les fonctions synchrones s'exécutent de manière asynchrone. Heureusement, cela est possible avec les rappels et les délais d'attente, respectivement.

Rappels

Supposons que nous avons trois fonctions asynchrones que nous voulons exécuter dans l' ordre, some_3secs_function, some_5secs_functionet some_8secs_function.

Étant donné que les fonctions peuvent être passées en tant qu'arguments en Javascript, vous pouvez transmettre une fonction en tant que rappel à exécuter une fois la fonction terminée.

Si nous créons les fonctions comme celle-ci

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

alors vous pouvez appeler alors dans l'ordre, comme ceci:

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

Timeouts

En Javascript, vous pouvez dire à une fonction de s'exécuter après un certain délai (en millisecondes). Cela peut, en effet, amener les fonctions synchrones à se comporter de manière asynchrone.

Si nous avons trois fonctions synchrones, nous pouvons les exécuter de manière asynchrone en utilisant la setTimeoutfonction.

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

Ceci est cependant un peu moche et viole le principe DRY [wikipedia] . Nous pourrions nettoyer un peu cela en créant une fonction qui accepte un tableau de fonctions et un délai d'expiration.

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

Cela peut être appelé ainsi:

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

En résumé, si vous avez des fonctions asynchrones que vous souhaitez exécuter de manière synchrone, utilisez des rappels, et si vous avez des fonctions synchrones que vous souhaitez exécuter de manière asynchrone, utilisez des délais d'expiration.


7
Cela ne retardera pas les fonctions de 3,5 et 8 secondes, comme suggéré par l'exemple, elles s'exécuteront simplement l'une après l'autre.
Trass Vasston

1
@Peter - Attendez, donc je suis confus. S'il s'agit de simples appels synchrones qui prennent quelques secondes, pourquoi avons-nous besoin de tout cela?
Wayne

9
@Peter - +1 pour la méthode la plus belle et compliquée que j'ai jamais vue pour appeler trois fonctions synchrones en séquence.
Wayne

4
Merci d'avoir expliqué de manière experte la différence entre les fonctions async et sync js. Cela explique tellement.
jnelson

2
Ce n'est PAS correct pour les raisons suivantes: (1) les 3 délais se résoudront tous après 10 secondes, donc les 3 lignes se déclenchent en même temps. (2) cette méthode nécessite que vous connaissiez la durée à l'avance et que les fonctions de "planification" se produiront dans le futur, au lieu d'attendre que les fonctions asynchrones antérieures de la chaîne se résolvent et que cela soit le déclencheur. --- à la place, vous souhaitez utiliser l'une des réponses suivantes en utilisant des rappels, des promesses ou la bibliothèque async.
zeroasterisk

37

Cette réponse utilise promises, une fonctionnalité JavaScript de la ECMAScript 6norme. Si votre plate-forme cible ne prend pas en charge promises, polyfill avec PromiseJs .

Regardez ma réponse ici Attendez qu'une fonction avec animations soit terminée jusqu'à ce que vous exécutiez une autre fonction si vous souhaitez utiliser des jQueryanimations.

Voici à quoi ressemblerait votre code avec ES6 Promiseset 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());
});

Les méthodes normales peuvent également être intégrées 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
});

La thenméthode est exécutée dès que le Promisefichier est terminé. Normalement, la valeur de retour du functionpassé à thenest transmise au suivant en tant que résultat.

Mais si a Promiseest renvoyé, la thenfonction suivante attend la Promisefin de l'exécution et en reçoit les résultats (la valeur à laquelle elle est passée fulfill).


Je sais que c'est utile, mais j'ai trouvé le code donné difficile à comprendre sans chercher un exemple du monde réel pour lui donner un contexte. J'ai trouvé cette vidéo sur YouTube: youtube.com/watch?v=y5mltEaQxa0 - et j'ai écrit la source de la vidéo ici drive.google.com/file/d/1NrsAYs1oaxXw0kv9hz7a6LjtOEb6x7z-/... Il y a quelques nuances supplémentaires comme la capture manquante dans cet exemple sur lequel cela développe. (utilisez un identifiant différent dans la ligne getPostById () ou essayez de changer le nom d'un auteur pour qu'il ne corresponde pas à un article, etc.)
JGFMK

20

Il semble que vous n'appréciez pas pleinement la différence entre l' exécution de fonctions synchrone et asynchrone .

Le code que vous avez fourni dans votre mise à jour exécute immédiatement chacune de vos fonctions de rappel, qui à leur tour démarrent immédiatement une animation. Les animations, cependant, s'exécutent de manière asyncronale . Cela fonctionne comme ceci:

  1. Effectuer une étape dans l'animation
  2. Appel setTimeoutavec une fonction contenant la prochaine étape d'animation et un délai
  3. Le temps passe
  4. Le rappel donné à setTimeoutexécute
  5. Revenir à l'étape 1

Cela continue jusqu'à la fin de la dernière étape de l'animation. En attendant, vos fonctions synchrones sont terminées depuis longtemps. En d'autres termes, votre appel à la animatefonction ne prend pas vraiment 3 secondes. L'effet est simulé avec des retards et des rappels.

Vous avez besoin d'une file d'attente . En interne, jQuery met les animations en file d'attente, n'exécutant votre rappel qu'une fois l'animation correspondante terminée. Si votre rappel démarre ensuite une autre animation, l'effet est qu'ils sont exécutés en séquence.

Dans le cas le plus simple, cela équivaut à ce qui suit:

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

Voici une fonction de bouclage asynchrone générale. Il appellera les fonctions données dans l'ordre, en attendant le nombre de secondes spécifié entre chacune.

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

Usage:

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

Vous pouvez évidemment modifier cela pour prendre des temps d'attente configurables ou pour exécuter immédiatement la première fonction ou pour arrêter l'exécution lorsqu'une fonction de la chaîne revient falseou aux applyfonctions dans un contexte spécifié ou tout ce dont vous pourriez avoir besoin.


14

Je pense que la bibliothèque async vous fournira un moyen très élégant de le faire. Alors que les promesses et les rappels peuvent être un peu difficiles à jongler avec, async peut donner des modèles nets pour rationaliser votre processus de pensée. Pour exécuter des fonctions en série, vous devez les placer dans une cascade asynchrone . Dans le jargon asynchrone, chaque fonction est appelée a taskqui prend quelques arguments et a callback; qui est la fonction suivante de la séquence. La structure de base ressemblerait à quelque chose comme:

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

J'ai juste essayé d'expliquer brièvement la structure ici. Lisez le guide de la cascade pour plus d'informations, il est plutôt bien écrit.


1
Cela rend vraiment JS un peu plus supportable.
mike

9

vos fonctions devraient prendre une fonction de rappel, qui est appelée lorsqu'elle se termine.

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

}

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

alors l'utilisation serait comme:

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

Je n'entrerai pas dans une discussion approfondie de setTimeout ici, mais:

  • dans ce cas, j'ai ajouté le code à exécuter sous forme de chaîne. c'est le moyen le plus simple de passer un var dans votre fonction setTimeout-ed, mais les puristes s'en plaindront.
  • vous pouvez également passer un nom de fonction sans guillemets, mais aucune variable ne peut être transmise.
  • votre code n'attend pas que setTimeout se déclenche.
  • Celui-ci peut être difficile à comprendre au début: à cause du point précédent, si vous passez une variable de votre fonction appelante, cette variable n'existera plus au moment où le timeout se déclenchera - la fonction appelante sera exécutée et c'est vars parti.
  • Je suis connu pour utiliser des fonctions anonymes pour contourner tout cela, mais il pourrait bien y avoir un meilleur moyen,

3

Puisque vous l'avez tagué avec javascript, j'irais avec un contrôle de minuterie puisque vos noms de fonction sont 3, 5 et 8 secondes. Alors démarrez votre chronomètre, 3 secondes après, appelez le premier, 5 secondes en appel le second, 8 secondes en appel le troisième, puis quand c'est fait, arrêtez le chronomètre.

Normalement, en Javascript, ce que vous avez est correct pour les fonctions qui s'exécutent les unes après les autres, mais comme il semble que vous essayez de faire une animation chronométrée, une minuterie serait votre meilleur pari.


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


Pouvez-vous expliquer ce que c'est? Qu'est-ce qui est lié au trait de soulignement? À quoi sert la fonction assignée next?
mtso

1
J'explique l'exemple 2 avec l'utilisation de jsfiddle. jsfiddle.net/mzsteyuy/3 Si vous me permettez d'expliquer grosso modo, l'exemple 2 est un court chemin du code dans jsfiddle. le trait de soulignement est Array dont les éléments sont valables au compteur (i) et fonctionnent ensuite et fonctionnent [0] ~ [2].
yuuya

1

Vous pouvez également utiliser les promesses de cette manière:

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

Vous devrez rendre some_valueglobal pour y accéder depuis l'intérieur du .then

Alternativement, à partir de la fonction externe, vous pouvez renvoyer la valeur que la fonction interne utiliserait, comme ceci:

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

0

J'utilise une fonction 'waitUntil' basée sur 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
};

Cela fonctionnerait parfaitement si vos fonctions définissaient une certaine condition sur true, que vous pourrez interroger. De plus, il est livré avec des délais d'expiration, ce qui vous offre des alternatives au cas où votre fonction échouerait à faire quelque chose (même dans la plage de temps. Pensez aux commentaires des utilisateurs!)

par exemple

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

Remarques

La date entraîne des estimations de délai d'attente approximatives. Pour plus de précision, passez à des fonctions telles que console.time (). Notez que Date offre un meilleur support multi-navigateurs et hérité. Si vous n'avez pas besoin de mesures exactes en millisecondes; ne vous embêtez pas, ou sinon, enveloppez-le et proposez console.time () lorsque le navigateur le prend en charge


0

Si la méthode 1 doit être exécutée après la méthode 2, 3, 4. L'extrait de code suivant peut être la solution pour cela en utilisant l'objet Deferred 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>

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.