web-dev-qa-db-fra.com

Moyen correct d'attendre la fin d'une fonction avant de continuer?

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. Ainsi, 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
};

Je suis venu avec 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 un moyen plus élégant de le gérer? Peut-être avec jQuery?

133
DA.

Une façon de traiter un travail asynchrone comme celui-ci consiste à 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 maintenant utiliser les fonctions fléchées pour obtenir la même chose. Par exemple:

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

113
Matt Way

Il semble qu'il vous manque un point important ici: JavaScript est un environnement d'exécution mono-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'êtes pas obligé d'attendre isPaused. Lorsque vous voyez l'alerte "Ici", isPaused sera déjà false et firstFunction sera revenu. En effet, vous ne pouvez pas "céder" à l'intérieur de la boucle for (// do something), la boucle ne peut pas être interrompue et devra être complétée en premier (plus de détails: traitement du fil Javascript et conditions de course ).

Cela dit, vous pouvez toujours faire en sorte que le flux de code à l'intérieur de firstFunction soit asynchrone et utilisez rappel ou promesse d'avertir l'appelant. Vous devez abandonner la boucle for et la simuler avec if à la place ( 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);
    });
}

En passant, JavaScript 1.7 a introduit le mot clé yield dans générateurs . Cela permettra de "poinçonner" les trous asynchrones dans un flux de code JavaScript sinon synchrone ( plus de détails, exemple ). Cependant, la prise en charge du navigateur pour les générateurs est actuellement limitée à Firefox et Chrome, autant que je sache.

45
noseratio

Utilisez async/wait:

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

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

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

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

Donc, JavaScript est mono-thread. Si vous ne passez pas un appel asynchrone, il se comportera de manière prévisible. Le thread JavaScript principal exécute complètement une fonction avant 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.

Pensez à la fonction synchrone comme une unité de travail atomique . Le thread JavaScript principal l'exécutera complètement, dans l'ordre dans lequel les instructions apparaissent dans le code.

Mais jetez 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 terminé, alors que l'appel ajax n'est pas presque terminé, même si makeAjaxCall() est retourné. LA COMPLICATION est que makeAjaxCall() a interrompu son travail en morceaux qui avancent petit à petit à chaque tour du fil JavaScript principal - il se comporte de manière asynchrone. Mais ce même thread principal, au cours d'une rotation/exécution, a exécuté les parties synchrones rapidement et de manière prévisible.

Donc, la façon dont je l'ai traité : Comme je l'ai dit, la fonction est l'unité de travail atomique. J'ai combiné le code des fonctions 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, y compris l'appel asynchrone, s'exécute de manière prévisible, dans l'ordre.

ALORS, lorsque l'appel asynchrone est terminé, après plusieurs tours du thread JavaScript principal, demandez-lui d'appeler la fonction 3. Ceci 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 dernière fonction souhaitée.

Je conviens que c'est plus compliqué. J'aime que le code soit symétrique, que les fonctions fassent une chose (ou presque) et que je n'aime pas que l'appel ajax soit en aucun cas responsable de l'affichage (créer une dépendance sur l'appelant). MAIS, avec un appel asynchrone intégré dans une fonction synchrone, des compromis doivent être faits afin de garantir l’ordre d’exécution. Et je dois coder pour IE 10, donc pas de promesse.

Résumé : Pour les appels synchrones, l'ordre de garantie est trivial. Chaque fonction s'exécute complètement 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 le moment où l'appel asynchrone est terminé et d'appeler la troisième fonction lorsque cet état est détecté.

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

En outre, une autre question similaire très cotée à ce sujet: Comment dois-je appeler 3 fonctions pour les exécuter l'une après l'autre?

5
kermit

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

Exemple rapide:

let firstFunction = new Promise(function(resolve, reject) {
    let x = 10
    let y = 0
    setTimeout(function(){
      for(i=0;i<x;i++){
         y++
      }
       console.log('loop completed')  
       resolve(y)
    }, 2000)
})

Tout d'abord, créez un Promise . La fonction ci-dessus sera complétée après 2s. J'ai utilisé setTimeout afin de démontrer la situation dans laquelle l'exécution des instructions prendrait un certain temps (sur la base de votre exemple).

async function secondFunction(){
    let result = await firstFunction
    console.log(result)
    console.log('next step')
}; 

secondFunction()

Pour la deuxième fonction, vous pouvez utiliser la fonction asynchrone/wait où vous attendez la fin de la première fonction avant de poursuivre. les instructions.

Remarque: vous pouvez simplement résoudre la promesse sans aucune valeur telle que resolve(). Dans mon exemple, j'ai résolu la promesse avec la valeur y que je pourrais ensuite utiliser dans la deuxième fonction.

J'espère que cela aidera les personnes qui visitent encore ce fil.

3
Jakub A Suplicki