Prawidłowy sposób oczekiwania na zakończenie jednej funkcji przed kontynuowaniem?

187

Mam dwie funkcje JS. Jeden dzwoni do drugiego. W ramach funkcji wywoływania chciałbym zadzwonić do drugiej, poczekać na zakończenie tej funkcji, a następnie kontynuować. Na przykład / pseudo kod:

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

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

Wymyśliłem to rozwiązanie, ale nie wiem, czy jest to mądry sposób, aby to zrobić.

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

Czy to jest legalne? Czy istnieje bardziej elegancki sposób, aby sobie z tym poradzić? Być może z jQuery?

DA.
źródło
11
Co firstFunctiondokładnie to powoduje, że jest asynchroniczny? Tak czy inaczej - sprawdź obietnice
zerkms
Pierwszą funkcją jest szybkie aktualizowanie zegara wyników co 10 sekundy. Które ... przychodzą na myśl, myślę, że moglibyśmy wstępnie obliczyć, a następnie po prostu ręcznie wstrzymać drugie wywołanie funkcji za pomocą setTimeout. To powiedziawszy, nadal widzę pragnienie posiadania zdolności pauzy gdzie indziej.
DA.
nie potrzebujesz przerwy - Google obietnic w jquery
Zerkms
@zerkms obietnice wygląda interesująco! Wciąż sprawdzam obsługę przeglądarki ...
DA.
1
Ja również mam ten sam problem.
Vappor Washmade

Odpowiedzi:

140

Jednym ze sposobów radzenia sobie z taką pracą asynchroniczną jest użycie funkcji zwrotnej, np .:

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

Zgodnie z sugestią @Janaka Pushpakumara, możesz teraz używać funkcji strzałek, aby osiągnąć to samo. Na przykład:

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


Aktualizacja: Odpowiedziałem na to jakiś czas temu i naprawdę chcę to zaktualizować. Chociaż wywołania zwrotne są absolutnie w porządku, z mojego doświadczenia wynika, że ​​powodują trudniejszy do odczytania i utrzymania kod. Są jednak sytuacje, w których nadal ich używam, takie jak przekazywanie zdarzeń w toku i tym podobnych jako parametrów. Ta aktualizacja ma na celu podkreślenie alternatyw.

Również oryginalne pytanie nie specificallty wspomnieć asynchronicznie, więc w przypadku gdy ktoś jest zdezorientowany, jeśli funkcja jest synchroniczny, to będą blokować kiedy dzwonił. Na przykład:

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

Jeśli choć sugeruje się, że funkcja jest asynchroniczna, moim dzisiejszym podejściem do asynchronicznej pracy jest asynchronizacja / czekanie. Na przykład:

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

IMO, async / await sprawia, że ​​kod jest znacznie bardziej czytelny niż bezpośrednie stosowanie obietnic (przez większość czasu). Jeśli potrzebujesz poradzić sobie z błędami przechwytywania, użyj go z try / catch. Przeczytaj o tym więcej tutaj: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function .

Matt Way
źródło
9
@zerkms - Chcesz opracować?
Matt Way
7
oddzwanianie jest niewygodne w radzeniu sobie z asynchronią, obietnice są znacznie łatwiejsze i bardziej elastyczne
zerkms
1
Chociaż masz rację, że nie powinienem był używać tego słowa najlepiej (zaktualizowanego), wygoda połączeń zwrotnych w porównaniu z obietnicami zależy od złożoności problemu.
Matt Way
3
Staje się offtopic, ale osobiście nie widzę powodu, aby w tych dniach preferować oddzwanianie :-) Nie pamiętam żadnego mojego kodu napisanego w ciągu ostatnich 2 lat, który zapewniał oddzwonienia zamiast obietnic.
zerkms
3
Jeśli chcesz, możesz użyć funkcji strzałki w es6 secondFunction () {firstFunction ((response) => {console.log (response);}); }
Janaka Pushpakumara,
53

Użyj asynchronicznie / oczekuj:

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

następnie użyj funkcji czekaj w innej funkcji, aby poczekać na jej powrót:

async function secondFunction(){
  await firstFunction();
  // now wait for firstFunction to finish...
  // do something else
};
Fawaz
źródło
9
dla tych z nas, którzy utknęli w obsłudze starszych przeglądarek, IE nie obsługuje asynchronizacji / oczekiwania
kyle
Czy można tego używać poza obiema funkcjami, jak w $(function() { await firstFunction(); secondFunction(); });,?
Lewistrick
1
@Lewistrick Pamiętaj tylko, że awaitmożna go używać tylko wewnątrz asyncmetody. Jeśli więc wprowadzisz funkcję rodzica async, możesz wywołać dowolną liczbę osób async methodsz awaittaką funkcją lub bez niej.
Fawaz
Czy to możliwe, aby ? awaitsetTimeout
Shayan
1
@Shayan tak, owinąć setTimeout w inną funkcję, która rozwiązuje obietnicę po upływie limitu czasu.
Fawaz
51

Wygląda na to, że brakuje Ci tutaj ważnej kwestii: JavaScript jest jednowątkowym środowiskiem wykonawczym. Spójrzmy jeszcze raz na twój kod, zauważ, że dodałem 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
        };
    }
};

Nie musisz czekać isPaused. Gdy zobaczysz alert „Tutaj”, isPausedbędzie falsejuż i firstFunctionpowróci. Wynika to z faktu, że nie można „ustąpić” z wnętrza forpętli ( // do something), pętla może nie zostać przerwana i najpierw trzeba będzie ją całkowicie ukończyć (więcej szczegółów: obsługa wątków JavaScript i warunki wyścigu ).

To powiedziawszy, nadal możesz sprawić, że kod przepływa wewnątrz, firstFunctionaby był asynchroniczny i użyć albo wywołania zwrotnego, albo obiecać powiadomienie dzwoniącego. Musiałbyś zrezygnować z forpętli i symulować ją za pomocą if( 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);
    });
}

Na marginesie, JavaScript 1.7 wprowadził yieldsłowo kluczowe w ramach generatorów . To pozwoli „dziurkować” asynchroniczne dziury w inaczej synchronicznym przepływie kodu JavaScript ( więcej szczegółów i przykład ). Jednak obsługa przeglądarki dla generatorów jest obecnie ograniczona do Firefoksa i Chrome, AFAIK.

noseratio
źródło
3
Uratowałeś mnie. $ .Deferred () jest tym, do czego dążyłem. Dzięki
Temitayo,
@noseratio Próbowałem tego. Ale metoda waitForIt w ogóle nie jest wywoływana. Co ja robię źle?
vigamage
@vigamage, czy możesz podać link do jsfiddle lub codepen tego, co próbowałeś?
noseratio
1
Witam, dziękuję za troskę. Mam to działa. Dziękuję Ci..!
vigamage
18

Eleganckim sposobem czekania na zakończenie jednej funkcji jest użycie Promises z funkcją asynchroniczną / oczekującą .


  1. Po pierwsze, stwórz obietnicę . Utworzona przeze mnie funkcja zostanie ukończona po 2 sekundach. użyłem setTimeout , aby zademonstrować sytuację, w której wykonanie instrukcji zajęłoby trochę czasu.
  2. W przypadku drugiej funkcji można użyć funkcji asynchronicznej / oczekującej, w której awaitnależy wykonać pierwszą funkcję przed wykonaniem instrukcji.

Przykład:

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


Uwaga:

Można po prostu bez wartości jak tak . W moim przykładzie z wartości , które można następnie wykorzystać w drugiej funkcji.resolvePromiseresolve()resolvedPromisey

Jakub A Suplicki
źródło
tak, to zawsze zadziała. jeśli ktokolwiek jest zaangażowany w tego typu scenariusze, JavaScript Promise zrobi to za nich.
Puttamarigowda MS
5

Jedynym problemem związanym z obietnicami jest to, że IE ich nie obsługuje. Edge ma, ale jest mnóstwo IE 10 i 11: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise (kompatybilność na dole)

JavaScript jest więc jednowątkowy. Jeśli nie wykonujesz połączenia asynchronicznego, będzie ono działało przewidywalnie. Główny wątek JavaScript wykona jedną funkcję całkowicie przed wykonaniem następnej, w kolejności, w jakiej występują w kodzie. Gwarantowanie kolejności funkcji synchronicznych jest banalne - każda funkcja wykona się całkowicie w kolejności, w której została wywołana.

Pomyśl o funkcji synchronicznej jako atomowej jednostce pracy . Główny wątek JavaScript wykona go w pełni, w kolejności, w jakiej instrukcje pojawiają się w kodzie.

Ale wrzuć wywołanie asynchroniczne, jak w następującej sytuacji:

showLoadingDiv(); // function 1

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

hideLoadingDiv(); // function 3

To nie robi tego, co chcesz . Natychmiast wykonuje funkcję 1, funkcję 2 i funkcję 3. Ładowanie div miga i zniknęło, podczas gdy wywołanie ajax nie jest prawie ukończone, mimo że makeAjaxCall()zostało zwrócone. POWIKŁANIE polega na tym, że makeAjaxCall()podzielił on swoją pracę na części, które są stopniowo zwiększane o każdy obrót głównego wątku JavaScript - zachowuje się asychronicznie. Ale ten sam główny wątek, podczas jednego wirowania / przebiegu, wykonał synchroniczne części szybko i przewidywalnie.

Sposób w jaki sobie z tym poradziłem : Jak powiedziałem, funkcją jest atomowa jednostka pracy. Połączyłem kod funkcji 1 i 2 - kod funkcji 1 umieściłem przed funkcją asynch. Pozbyłem się funkcji 1. Wszystko, włącznie z wywołaniem asynchronicznym, wykonuje się przewidywalnie, w kolejności.

NASTĘPNIE, gdy zakończy się asynchroniczne wywołanie, po kilku obrotach głównego wątku JavaScript, niech wywoła funkcję 3. To gwarantuje kolejność . Na przykład w przypadku ajax program obsługi zdarzeń onreadystatechange jest wywoływany wiele razy. Kiedy zgłosi, że jest zakończone, wywołaj ostatnią żądaną funkcję.

Zgadzam się, że jest bałagan. Lubię, żeby kod był symetryczny, lubię, żeby funkcje wykonywały jedną rzecz (lub blisko niego) i nie lubię, aby wywołanie ajax w jakikolwiek sposób odpowiadało za wyświetlanie (tworzenie zależności od wywołującego). ALE, z asynchronicznym wywołaniem wbudowanym w funkcję synchroniczną, konieczne są kompromisy w celu zagwarantowania kolejności wykonania. I muszę kodować dla IE 10, więc nie obiecuję.

Podsumowanie : W przypadku połączeń synchronicznych zagwarantowanie porządku jest banalne. Każda funkcja wykonuje się w pełni w kolejności, w jakiej została wywołana. W przypadku funkcji z wywołaniem asynchronicznym jedynym sposobem na zagwarantowanie kolejności jest monitorowanie, kiedy zakończy się wywołanie asynchroniczne, i wywoływanie trzeciej funkcji po wykryciu tego stanu.

Dyskusje na temat wątków JavaScript można znaleźć na stronie : https://medium.com/@francesco_rizzi/javascript-main-thread-dissected-43c85fce7e23 i https://developer.mozilla.org/en-US/docs/Web/JavaScript/ EventLoop

Kolejne podobne, wysoko ocenione pytanie na ten temat: Jak wywołać 3 funkcje, aby wykonać je jedna po drugiej?

Kermit
źródło
8
Do downvoter (s) jestem ciekawy, co jest nie tak z tą odpowiedzią.
kermit
0

To wymyśliłem, ponieważ muszę uruchomić kilka operacji w łańcuchu.

<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
źródło
Dodaj przynajmniej wysoki komentarz do opisu danego fragmentu kodu. Ma to na celu wyjaśnienie, w jaki sposób Twój kod rozwiązuje problem.
Cold Cerberus
Problem polegał na uruchomieniu drugiej funkcji po pierwszej. Pokazuję, jak uruchomić drugą funkcję po pierwszej, a także jak uruchomić trzecią funkcję po drugiej. Zrobiłem trzy funkcje, aby ułatwić zrozumienie wymaganego kodu.
Niclas Arnberger
0

Twoja główna zabawa zadzwoni jako pierwsza, a następnie po jej zakończeniu zadzwoni kolejna zabawa.

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
źródło