Comment attendre la fin d'une fonction avant de continuer?

187

J'ai deux fonctions JS. L'un appelle l'autre. Dans la fonction d'appel, j'aimerais appeler l'autre, attendre que cette fonction se termine, puis continuer. Donc, par exemple / pseudo code:

function firstFunction(){
    for(i=0;i<x;i++){
        // do something
    }
};

function secondFunction(){
    firstFunction()
    // now wait for firstFunction to finish...
    // do something else
};

J'ai trouvé cette solution, mais je ne sais pas si c'est une façon intelligente de s'y prendre.

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

Est-ce légitime? Y a-t-il une manière plus élégante de le gérer? Peut-être avec jQuery?

DA.
la source
11
Qu'est- firstFunctionce que fait exactement qui le rend asynchrone? De toute façon - Vérifiez les promesses
Zerkms
La première fonction met à jour rapidement une horloge de score tous les dixièmes de seconde. Ce qui ... en y réfléchissant bien, je suppose que nous pourrions pré-calculer puis mettre en pause manuellement le deuxième appel de fonction via setTimeout. Cela dit, je peux encore voir un désir d'avoir une capacité de pause ailleurs.
DA.
vous n'avez pas besoin d'une pause - google pour les promesses dans jquery
zerkms
Les promesses de @zerkms semblent intéressantes! Enquête sur le support du navigateur ...
DA.
1
J'ai aussi le même problème.
Vappor Washmade

Réponses:

142

Une façon de gérer un travail asynchrone comme celui-ci est d'utiliser une fonction de rappel, par exemple:

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

Selon la suggestion de @Janaka Pushpakumara, vous pouvez désormais utiliser les fonctions fléchées pour réaliser la même chose. Par exemple:

firstFunction(() => console.log('huzzah, I\'m done!'))


Mise à jour: j'ai répondu à cela il y a un certain temps et je veux vraiment le mettre à jour. Bien que les rappels soient tout à fait corrects, d'après mon expérience, ils ont tendance à produire du code plus difficile à lire et à maintenir. Il y a des situations où je les utilise encore, par exemple pour passer des événements en cours, etc., en tant que paramètres. Cette mise à jour est juste pour souligner les alternatives.

De plus, la question d'origine ne mentionne pas spécifiquement async, donc au cas où quelqu'un serait confus, si votre fonction est synchrone, elle se bloquera lorsqu'elle sera appelée. Par exemple:

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

Si, comme implicite, la fonction est asynchrone, la façon dont j'ai tendance à gérer tout mon travail asynchrone aujourd'hui est async / await. Par exemple:

const secondFunction = async () => {
  const result = await firstFunction()
  // do something else here after firstFunction completes
}

IMO, async / await rend votre code beaucoup plus lisible que d'utiliser directement les promesses (la plupart du temps). Si vous devez gérer des erreurs de capture, utilisez-le avec try / catch. En savoir plus ici: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function .

Matt Way
la source
9
@zerkms - Voulez-vous élaborer?
Matt Way
7
les rappels ne sont pas pratiques pour gérer l'asynchronisme, les promesses sont beaucoup plus faciles et flexibles
zerkms
1
Bien que vous ayez raison de dire que je n'aurais pas dû utiliser le mot meilleur (mis à jour), la commodité des rappels par rapport aux promesses dépend de la complexité du problème.
Matt Way
3
Cela devient hors-sujet, mais personnellement, je ne vois pas de raison de préférer les rappels ces jours-ci :-) Je ne me souviens d'aucun de mes codes écrits au cours des 2 dernières années avec des rappels fournis par rapport aux promesses.
zerkms
3
Si vous le souhaitez, vous pouvez utiliser la fonction fléchée dans es6 secondFunction () {firstFunction ((response) => {console.log (response);}); }
Janaka Pushpakumara
53

Utilisez async / await:

async function firstFunction(){
  for(i=0;i<x;i++){
    // do something
  }
  return;
};

puis utilisez await dans votre autre fonction pour attendre son retour:

async function secondFunction(){
  await firstFunction();
  // now wait for firstFunction to finish...
  // do something else
};
Fawaz
la source
9
pour ceux d'entre nous qui supportent les navigateurs plus anciens, IE ne prend pas en charge async / await
kyle
Cela peut-il être utilisé en dehors des deux fonctions, comme dans $(function() { await firstFunction(); secondFunction(); });,?
Lewistrick
1
@Lewistrick Souvenez-vous simplement awaitqu'il ne peut être utilisé qu'à l'intérieur d'une asyncméthode. Donc, si vous faites fonctionner votre parent, asyncvous pouvez en appeler autant à l' async methodsintérieur awaitque vous le souhaitez.
Fawaz
Est-il possible de faire awaitun setTimeout?
Shayan
1
@Shayan oui, enveloppez setTimeout dans une autre fonction qui résout une promesse après l'expiration du délai.
Fawaz
51

Il semble qu'il vous manque un point important ici: JavaScript est un environnement d'exécution à un seul thread. Regardons à nouveau votre code, notez que j'ai ajouté 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
        };
    }
};

Vous n'avez pas à attendre isPaused. Lorsque vous voyez l'alerte "Ici", isPausedsera falsedéjà, et firstFunctionsera de retour. C'est parce que vous ne pouvez pas "céder" de l'intérieur de la forboucle ( // do something), la boucle ne peut pas être interrompue et devra d'abord se terminer complètement (plus de détails: gestion des threads Javascript et conditions de course ).

Cela dit, vous pouvez toujours faire en sorte que le code circule à l'intérieur firstFunctionpour être asynchrone et utiliser un rappel ou une promesse pour notifier l'appelant. Vous devrez abandonner la forboucle et la simuler avec à la ifplace ( 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);
    });
}

Par ailleurs, JavaScript 1.7 a introduit le yieldmot-clé dans le cadre des générateurs . Cela permettra de "percer" des trous asynchrones dans un flux de code JavaScript autrement synchrone ( plus de détails et un exemple ). Cependant, le support du navigateur pour les générateurs est actuellement limité à Firefox et Chrome, AFAIK.

noseratio
la source
3
Tu m'as sauvé. $ .Deferred () est ce que je cherche. Merci
Temitayo
@noseratio J'ai essayé ça. Mais la méthode waitForIt n'est pas du tout appelée. Qu'est-ce que je fais mal?
vigamage le
@vigamage, pourriez-vous fournir un lien vers jsfiddle ou codepen de ce que vous avez essayé?
noseratio
1
Bonjour, Merci pour votre sollicitude. Je l'ai fait fonctionner. Merci..!
vigamage
18

Une manière élégante d'attendre qu'une fonction soit terminée en premier est d'utiliser Promises avec la fonction async / await .


  1. Tout d'abord, créez une promesse . La fonction que j'ai créée sera terminée au bout de 2 s. J'ai utilisé setTimeoutpour démontrer la situation où les instructions prendraient un certain temps à s'exécuter.
  2. Pour la deuxième fonction, vous pouvez utiliser la fonction async / await où vous devrez terminer awaitla première fonction avant de suivre les instructions.

Exemple:

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


Remarque:

Vous pouvez simplement resolvele Promisesans aucune valeur comme ça resolve(). Dans mon exemple, je resolvedle Promiseavec la valeur de yque je peux ensuite utiliser dans la deuxième fonction.

Jakub A Suplicki
la source
oui cela fonctionnera toujours. si quelqu'un est impliqué dans ce genre de scénarios, JavaScript Promise fera l'affaire pour eux.
Puttamarigowda MS le
5

Le seul problème avec les promesses est que IE ne les prend pas en charge. Edge le fait, mais il y a beaucoup d'IE 10 et 11 là-bas: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise (compatibilité en bas)

Donc, JavaScript est monothread. Si vous n'effectuez pas d'appel asynchrone, il se comportera de manière prévisible. Le thread JavaScript principal exécutera une fonction complètement avant d'exécuter la suivante, dans l'ordre dans lequel elles apparaissent dans le code. Garantir l'ordre des fonctions synchrones est trivial - chaque fonction s'exécutera complètement dans l'ordre dans lequel elle a été appelée.

Considérez la fonction synchrone comme une unité atomique de travail . Le thread JavaScript principal l'exécutera entièrement, dans l'ordre dans lequel les instructions apparaissent dans le code.

Mais, lancez l'appel asynchrone, comme dans la situation suivante:

showLoadingDiv(); // function 1

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

hideLoadingDiv(); // function 3

Cela ne fait pas ce que vous voulez . Il exécute instantanément la fonction 1, la fonction 2 et la fonction 3. Le chargement de div clignote et il est parti, alors que l'appel ajax n'est pas presque terminé, même s'il makeAjaxCall()est retourné. LA COMPLICATION est qu'il makeAjaxCall()a brisé son travail en morceaux qui sont avancés petit à petit à chaque rotation du thread JavaScript principal - il se comporte de manière asychronique. Mais ce même thread principal, pendant un spin / run, a exécuté les parties synchrones rapidement et de manière prévisible.

Donc, la façon dont je l'ai géré : comme je l'ai dit, la fonction est l'unité atomique de travail. J'ai combiné le code de la fonction 1 et 2 - J'ai mis le code de la fonction 1 dans la fonction 2, avant l'appel asynchrone. Je me suis débarrassé de la fonction 1. Tout jusqu'à et y compris l'appel asynchrone s'exécute de manière prévisible, dans l'ordre.

ALORS, lorsque l'appel asynchrone se termine, après plusieurs tours du thread JavaScript principal, faites-lui appeler la fonction 3. Cela garantit l'ordre . Par exemple, avec ajax, le gestionnaire d'événements onreadystatechange est appelé plusieurs fois. Lorsqu'il signale qu'il est terminé, appelez la fonction finale souhaitée.

Je suis d'accord que c'est plus compliqué. J'aime que le code soit symétrique, j'aime que les fonctions fassent une chose (ou s'en rapprochent), et je n'aime pas que l'appel ajax soit en aucune façon responsable de l'affichage (créant une dépendance sur l'appelant). MAIS, avec un appel asynchrone incorporé dans une fonction synchrone, des compromis doivent être faits pour garantir l'ordre d'exécution. Et je dois coder pour IE 10 donc pas de promesses.

Résumé : Pour les appels synchrones, garantir l'ordre est trivial. Chaque fonction s'exécute entièrement dans l'ordre dans lequel elle a été appelée. Pour une fonction avec un appel asynchrone, le seul moyen de garantir l'ordre est de surveiller la fin de l'appel asynchrone et d'appeler la troisième fonction lorsque cet état est détecté.

Pour une discussion sur les fils JavaScript, voir: https://medium.com/@francesco_rizzi/javascript-main-thread-dissected-43c85fce7e23 et https://developer.mozilla.org/en-US/docs/Web/JavaScript/ EventLoop

Aussi, une autre question similaire et très appréciée à ce sujet: comment appeler 3 fonctions pour les exécuter l'une après l'autre?

kermit
la source
8
Je serais curieux de savoir ce qui ne va pas avec cette réponse.
kermit le
0

C'est ce que j'ai imaginé, car je dois exécuter plusieurs opérations dans une chaîne.

<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
la source
Veuillez ajouter au moins un commentaire de haut niveau pour la description de l'extrait de code donné. Ceci explique comment votre code résout le problème.
Cold Cerberus
Le problème était d'exécuter une deuxième fonction après la première. Je montre comment exécuter une deuxième fonction après la première, et comment exécuter une troisième fonction après la deuxième. J'ai créé trois fonctions, pour faciliter la compréhension du code requis.
Niclas Arnberger
0

Votre plaisir principal s'appellera firstFun, puis votre prochain plaisir appellera.

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
la source