Richtig warten, bis eine Funktion beendet ist, bevor Sie fortfahren?

186

Ich habe zwei JS-Funktionen. Einer ruft den anderen an. Innerhalb der aufrufenden Funktion möchte ich die andere aufrufen, warten, bis diese Funktion beendet ist, und dann fortfahren. Also zum Beispiel / Pseudocode:

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

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

Ich habe diese Lösung gefunden, weiß aber nicht, ob dies ein kluger Weg ist.

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

Ist das echt? Gibt es eine elegantere Art, damit umzugehen? Vielleicht mit jQuery?

DA.
quelle
11
Was firstFunctiongenau macht es asynchron? So oder so - überprüfen Sie über Versprechen
zerkms
Die erste Funktion aktualisiert schnell alle 10 Sekunden eine Score-Uhr. Was ... ich denke, wir könnten den zweiten Funktionsaufruf über setTimeout vorberechnen und dann einfach manuell anhalten. Trotzdem sehe ich immer noch den Wunsch, anderswo eine Pausenfähigkeit zu haben.
DA.
Sie brauchen keine Pause - Google für Versprechen in JQuery
Zerkms
@ zerkms Versprechen sieht interessant aus! Untersucht noch die Browserunterstützung ...
DA.
1
Ich habe auch das gleiche Problem.
Vappor Washmade

Antworten:

140

Eine Möglichkeit, mit asynchroner Arbeit wie dieser umzugehen, ist die Verwendung einer Rückruffunktion, z.

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

Gemäß dem Vorschlag von @Janaka Pushpakumara können Sie jetzt Pfeilfunktionen verwenden, um dasselbe zu erreichen. Beispielsweise:

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


Update: Ich habe dies vor einiger Zeit beantwortet und möchte es wirklich aktualisieren. Rückrufe sind zwar in Ordnung, führen jedoch meiner Erfahrung nach zu Code, der schwieriger zu lesen und zu warten ist. Es gibt Situationen, in denen ich sie immer noch verwende, z. B. um laufende Ereignisse und dergleichen als Parameter zu übergeben. Dieses Update dient nur zur Hervorhebung von Alternativen.

Auch die ursprüngliche Frage ist specificallty nicht async erwähnen, so falls jemand verwirrt ist, wenn Ihre Funktion synchron ist, es wird blockiert , wenn sie aufgerufen. Beispielsweise:

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

Wenn die Funktion, wie impliziert, asynchron ist, ist die Art und Weise, wie ich heute mit all meiner asynchronen Arbeit umgehe, asynchron / warten. Beispielsweise:

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

IMO, async / await macht Ihren Code viel lesbarer als die direkte Verwendung von Versprechungen (meistens). Wenn Sie mit Fangfehlern umgehen müssen, verwenden Sie sie mit try / catch. Lesen Sie hier mehr darüber: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function .

Matt Way
quelle
9
@zerkms - Möchtest du das näher erläutern?
Matt Way
7
Rückrufe sind unpraktisch, um mit Asynchronität umzugehen, Versprechen sind viel einfacher und flexibler
zerkms
1
Während Sie Recht haben, dass ich das Wort nicht am besten (aktualisiert) hätte verwenden sollen, hängt die Bequemlichkeit von Rückrufen gegenüber Versprechungen von der Komplexität des Problems ab.
Matt Way
3
Es wird offtopisch, aber ich persönlich sehe keinen Grund, Rückrufe heutzutage zu bevorzugen :-) Ich kann mich an keinen meiner Codes erinnern, die in den letzten 2 Jahren geschrieben wurden, wenn Rückrufe Versprechungen vorgezogen wurden.
Zerkms
3
Wenn Sie möchten, können Sie die Pfeilfunktion in es6 verwenden. SecondFunction () {firstFunction ((response) => {console.log (response);}); }
Janaka Pushpakumara
54

Verwenden Sie async / await:

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

Verwenden Sie dann await in Ihrer anderen Funktion, um auf die Rückkehr zu warten:

async function secondFunction(){
  await firstFunction();
  // now wait for firstFunction to finish...
  // do something else
};
Fawaz
quelle
9
Für diejenigen von uns, die ältere Browser nicht unterstützen, unterstützt IE nicht async / await
kyle
Kann dies außerhalb beider Funktionen wie in verwendet werden $(function() { await firstFunction(); secondFunction(); });?
Lewistrick
1
@Lewistrick Denken Sie daran, awaitkann nur innerhalb einer asyncMethode verwendet werden. Wenn Sie also Ihre übergeordnete Funktion asyncaktivieren, können Sie beliebig viele async methodsBenutzer mit oder ohne Funktion aufrufen await.
Fawaz
Ist es möglich zu awaiteinem setTimeout?
Shayan
1
@Shayan ja, es ist, packe setTimeout in eine andere Funktion, die ein Versprechen nach dem Timeout auflöst.
Fawaz
51

Anscheinend fehlt Ihnen hier ein wichtiger Punkt: JavaScript ist eine Single-Threaded-Ausführungsumgebung. Schauen wir uns Ihren Code noch einmal an. Beachten Sie, dass ich Folgendes hinzugefügt habe 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
        };
    }
};

Sie müssen nicht warten isPaused. Wenn Sie die Warnung "Hier" sehen, isPausedwird sie falsebereits angezeigt und firstFunctionist zurückgekehrt. Das liegt daran, dass Sie nicht innerhalb der forSchleife ( // do something) "nachgeben" können. Die Schleife wird möglicherweise nicht unterbrochen und muss zuerst vollständig abgeschlossen werden (weitere Details: Javascript-Thread-Behandlung und Race-Bedingungen ).

Trotzdem können Sie den Codefluss im Inneren firstFunctionasynchron gestalten und entweder Rückruf oder Versprechen verwenden, um den Anrufer zu benachrichtigen. Sie müssten diefor Schleife aufgeben und ifstattdessen mit simulieren ( 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);
    });
}

Nebenbei bemerkt hat JavaScript 1.7 yieldSchlüsselwörter als Teil von Generatoren eingeführt . Dadurch können asynchrone Löcher in den ansonsten synchronen JavaScript-Codefluss "gestanzt" werden ( weitere Details und ein Beispiel ). Die Browserunterstützung für Generatoren ist derzeit jedoch auf Firefox und Chrome, AFAIK, beschränkt.

noseratio
quelle
3
Du hast mich gerettet. $ .Deferred () ist das, worauf ich geschossen habe. Vielen Dank
Temitayo
@noseratio Ich habe es versucht. Die waitForIt-Methode wird jedoch überhaupt nicht aufgerufen. Was mache ich falsch?
Vigamage
@vigamage, könnten Sie einen Link zu jsfiddle oder codepen von dem bereitstellen, was Sie versucht haben?
Noseratio
1
Hallo, vielen Dank für Ihre Sorge. Ich habe es zum Laufen gebracht. Danke..!
Vigamage
18

Eine elegante Möglichkeit, darauf zu warten, dass eine Funktion zuerst ausgeführt wird, besteht darin, Promises mit der Funktion async / await zu verwenden.


  1. Erstellen Sie zunächst ein Versprechen . Die von mir erstellte Funktion wird nach 2s abgeschlossen sein. Ich habe verwendet setTimeout, um die Situation zu demonstrieren, in der die Ausführung der Anweisungen einige Zeit in Anspruch nehmen würde.
  2. Für die zweite Funktion können Sie die Funktion async / await verwenden, awaitum die erste Funktion abzuschließen, bevor Sie mit den Anweisungen fortfahren.

Beispiel:

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


Hinweis:

Sie könnten einfach resolvedas Promiseohne irgendeinen Wert so resolve(). In meinem Beispiel habe ich resolvedden Promisemit dem Wert y, den ich dann in der zweiten Funktion verwenden kann.

Jakub A Suplicki
quelle
Danke, es funktioniert!
modInfo
Ja, das wird immer funktionieren. Wenn jemand an solchen Szenarien beteiligt ist, erledigt JavaScript Promise den Trick für ihn.
Puttamarigowda MS vor
6

Das einzige Problem mit Versprechungen ist, dass der IE sie nicht unterstützt. Edge tut es, aber es gibt viele IE 10 und 11 da draußen: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise (Kompatibilität unten)

JavaScript ist also Single-Threaded. Wenn Sie keinen asynchronen Anruf tätigen, verhält sich dieser vorhersehbar. Der Haupt-JavaScript-Thread führt eine Funktion vollständig aus, bevor die nächste in der Reihenfolge ausgeführt wird, in der sie im Code angezeigt werden. Die Garantie der Reihenfolge für synchrone Funktionen ist trivial - jede Funktion wird vollständig in der Reihenfolge ausgeführt, in der sie aufgerufen wurde.

Stellen Sie sich die synchrone Funktion als atomare Arbeitseinheit vor . Der Haupt-JavaScript-Thread führt es vollständig aus, in der Reihenfolge, in der die Anweisungen im Code erscheinen.

Aber werfen Sie den asynchronen Aufruf wie in der folgenden Situation ein:

showLoadingDiv(); // function 1

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

hideLoadingDiv(); // function 3

Das macht nicht was du willst . Es führt sofort Funktion 1, Funktion 2 und Funktion 3 aus. Das Laden von div blinkt und ist weg, während der Ajax-Aufruf bei weitem nicht abgeschlossen ist, obwohl er makeAjaxCall()zurückgekehrt ist. Die Komplikation ist, dass makeAjaxCall()die Arbeit in Blöcke aufgeteilt wurde , die nach und nach durch jede Drehung des Haupt-JavaScript-Threads erweitert werden - sie verhält sich asychron. Derselbe Haupt-Thread führte jedoch während eines Drehs / Laufs die synchronen Teile schnell und vorhersehbar aus.

So habe ich damit umgegangen : Wie gesagt, die Funktion ist die atomare Arbeitseinheit. Ich habe den Code von Funktion 1 und 2 kombiniert - ich habe den Code von Funktion 1 vor dem asynchronen Aufruf in Funktion 2 eingefügt. Ich habe Funktion 1 losgeworden. Alles bis einschließlich des asynchronen Aufrufs wird vorhersehbar in der richtigen Reihenfolge ausgeführt.

Wenn der asynchrone Aufruf abgeschlossen ist, rufen Sie nach mehreren Drehungen des JavaScript-Hauptthreads die Funktion 3 auf. Dies garantiert die Reihenfolge . Mit ajax wird beispielsweise der Ereignishandler onreadystatechange mehrmals aufgerufen. Wenn es meldet, dass es abgeschlossen ist, rufen Sie die gewünschte endgültige Funktion auf.

Ich bin damit einverstanden, dass es chaotischer ist. Ich mag es, wenn Code symmetrisch ist, ich mag es, wenn Funktionen eine Sache tun (oder in der Nähe davon), und ich mag es nicht, wenn der Ajax-Aufruf in irgendeiner Weise für die Anzeige verantwortlich ist (wodurch eine Abhängigkeit vom Aufrufer entsteht). ABER bei einem in eine synchrone Funktion eingebetteten asynchronen Aufruf müssen Kompromisse eingegangen werden, um die Ausführungsreihenfolge zu gewährleisten. Und ich muss für IE 10 codieren, also keine Versprechen.

Zusammenfassung : Bei synchronen Anrufen ist die Garantie der Bestellung trivial. Jede Funktion wird vollständig in der Reihenfolge ausgeführt, in der sie aufgerufen wurde. Bei einer Funktion mit einem asynchronen Aufruf besteht die einzige Möglichkeit, die Reihenfolge zu gewährleisten, darin, zu überwachen, wann der asynchrone Aufruf abgeschlossen ist, und die dritte Funktion aufzurufen, wenn dieser Status erkannt wird.

Eine Diskussion der JavaScript-Threads finden Sie unter: https://medium.com/@francesco_rizzi/javascript-main-thread-dissected-43c85fce7e23 und https://developer.mozilla.org/en-US/docs/Web/JavaScript/ EventLoop

Eine weitere ähnliche, hoch bewertete Frage zu diesem Thema: Wie soll ich 3 Funktionen aufrufen, um sie nacheinander auszuführen?

Kermit
quelle
8
Für die Downvoter wäre ich neugierig zu wissen, was mit dieser Antwort falsch ist.
Kermit
Möglicherweise passiert zu viel Formatierung? Inhaltlich scheint es mir in Ordnung zu sein
tschoppi
0

Das habe ich mir ausgedacht, da ich mehrere Operationen in einer Kette ausführen muss.

<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
quelle
Bitte fügen Sie mindestens einen Kommentar auf hoher Ebene hinzu, um eine Beschreibung des angegebenen Snippets zu erhalten. Hier erfahren Sie, wie Ihr Code das Problem behebt.
Kalter Cerberus
Das Problem bestand darin, eine zweite Funktion nach der ersten auszuführen. Ich zeige, wie eine zweite Funktion nach der ersten ausgeführt wird und wie eine dritte Funktion nach der zweiten ausgeführt wird. Ich habe drei Funktionen erstellt, um zu verstehen, welcher Code erforderlich ist.
Niclas Arnberger
0

Ihr Hauptspaß wird zuerstFun nennen, und wenn Sie fertig sind, wird Ihr nächster Spaß anrufen.

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
quelle