Modo corretto di attendere il completamento di una funzione prima di continuare?


187

Ho due funzioni JS. Uno chiama l'altro. All'interno della funzione di chiamata, vorrei chiamare l'altro, attendere il completamento di tale funzione, quindi continuare. Quindi, ad esempio / pseudo codice:

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

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

Ho trovato questa soluzione, ma non so se questo è un modo intelligente per farlo.

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

È legittimo? Esiste un modo più elegante per gestirlo? Forse con jQuery?


11
Cosa fa firstFunctionesattamente ciò che lo rende asincrono? Ad ogni modo - controlla le promesse
zerkms

La prima funzione aggiorna rapidamente un orologio di punteggio ogni 10 di secondo. Quale ... vieni a pensarci bene, suppongo che potremmo pre-calcolare e quindi semplicemente mettere in pausa manualmente la seconda chiamata di funzione tramite setTimeout. Detto questo, posso ancora vedere il desiderio di avere una capacità di pausa altrove.
DA.

non hai bisogno di una pausa - google per le promesse di jquery
zerkms,

Le promesse di @zerkms sembrano interessanti! Ancora indagando sul supporto del browser ...
DA.

1
Ho anche lo stesso problema.
Vappor Washmade,

Risposte:


141

Un modo per gestire il lavoro asincrono in questo modo è utilizzare una funzione di richiamata, ad esempio:

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

Come suggerito da @Janaka Pushpakumara, ora puoi usare le funzioni freccia per ottenere la stessa cosa. Per esempio:

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


Aggiornamento: ho risposto abbastanza tempo fa e voglio davvero aggiornarlo. Mentre i callback sono assolutamente soddisfacenti, nella mia esperienza tendono a generare codice che è più difficile da leggere e mantenere. Ci sono situazioni in cui le uso ancora, ad esempio per passare in corso eventi e simili come parametri. Questo aggiornamento serve solo a sottolineare le alternative.

Anche la domanda iniziale non menziona specificallty asincrono, così nel caso qualcuno è confuso, se la funzione è sincrono, che si bloccano quando viene chiamato. Per esempio:

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

Se sebbene implicitamente la funzione sia asincrona, il modo in cui tendo a gestire tutto il mio lavoro asincrono oggi è con asincrono / wait. Per esempio:

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

IMO, async / waitit rende il tuo codice molto più leggibile rispetto all'utilizzo diretto delle promesse (il più delle volte). Se è necessario gestire gli errori di cattura, utilizzarlo con try / catch. Per saperne di più qui: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function .


9
@zerkms - Ti interessa elaborare?
Matt Way,

7
i callback sono scomodi per gestire l'asincronia, le promesse sono molto più facili e flessibili
zerkms

1
Mentre hai ragione sul fatto che non avrei dovuto usare la parola migliore (aggiornata), la convenienza di callback contro promesse dipende dalla complessità del problema.
Matt Way,

3
Diventa offtopico, ma personalmente non vedo un motivo per preferire i callback in questi giorni :-) Non riesco a ricordare nessuno dei miei codici scritti negli ultimi 2 anni a condizione che i callback abbiano richiamato sulle promesse.
zerkms,

3
Se vuoi, puoi usare la funzione freccia in es6 secondFunction () {firstFunction ((response) => {console.log (response);}); }
Janaka Pushpakumara,

53

Usa asincrono / attendi:

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

quindi usa wait nell'altra tua funzione per attendere che ritorni:

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

9
per quelli di noi bloccati a supportare i browser più vecchi, IE non supporta async / wait
kyle

Può essere usato al di fuori di entrambe le funzioni, come in $(function() { await firstFunction(); secondFunction(); });,?
Lewistrick,

1
@Lewistrick Basta ricordare che awaitpuò essere utilizzato solo all'interno di un asyncmetodo. Quindi, se fai funzionare il tuo genitore, asyncpuoi chiamarne tanti async methodsall'interno con o senza awaitquello che desideri.
Fawaz,

È possibile awaitun setTimeout?
Shayan,

1
@Shayan sì, avvolgi setTimeout in un'altra funzione che risolve una promessa dopo il timeout.
Fawaz,

51

Sembra che qui manchi un punto importante: JavaScript è un ambiente di esecuzione a thread singolo. Diamo un'occhiata al tuo codice, nota che ho aggiunto 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
        };
    }
};

Non devi aspettare isPaused. Quando viene visualizzato l'avviso "Qui", isPausedsarà falsegià e firstFunctionsarà restituito. Questo perché non è possibile "cedere" dall'interno del forloop ( // do something), il loop potrebbe non essere interrotto e dovrà prima completare completamente (maggiori dettagli: gestione dei thread Javascript e condizioni di gara ).

Detto questo, è ancora possibile rendere il flusso del codice interno firstFunctionasincrono e utilizzare callback o promessa per avvisare il chiamante. Dovresti rinunciare al forloop e simularlo con ifinvece ( 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);
    });
}

Per contro, JavaScript 1.7 ha introdotto la yieldparola chiave come parte dei generatori . Ciò consentirà di "perforare" buchi asincroni in un flusso di codice JavaScript altrimenti sincrono ( maggiori dettagli e un esempio ). Tuttavia, il supporto del browser per i generatori è attualmente limitato a Firefox e Chrome, AFAIK.


3
Mi hai salvato. $ .Deferred () è ciò per cui ho puntato. Grazie
Temitayo il

@noseratio Ho provato questo. Ma il metodo waitForIt non viene chiamato affatto. Che cosa sto facendo di sbagliato?
vigamage

@vigamage, potresti fornire un link a jsfiddle o codepen di ciò che hai provato?
noseratio,

1
Ciao, grazie per la tua preoccupazione. L'ho fatto funzionare. Grazie..!
vigamage

18

Un modo elegante di attendere che una funzione venga completata per prima consiste nell'utilizzare Promises con la funzione asincrona / wait .


  1. Innanzitutto, crea una promessa . La funzione che ho creato sarà completata dopo 2 secondi. Ho usato setTimeoutper dimostrare la situazione in cui l'esecuzione delle istruzioni richiederebbe del tempo.
  2. Per la seconda funzione, è possibile utilizzare la funzione asincrona / wait in cui si awaitcompleterà la prima funzione prima di procedere con le istruzioni.

Esempio:

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


Nota:

Si potrebbe semplicemente resolveil Promisesenza alcun valore in questo modo resolve(). Nel mio esempio, ho resolvedil Promisevalore di yquello che posso quindi usare nella seconda funzione.


sì, funzionerà sempre. se qualcuno coinvolto in questo tipo di scenari, allora Promessa JavaScript farà il trucco per loro.
Puttamarigowda MS

5

L'unico problema con le promesse è che IE non le supporta. Edge fa, ma c'è un sacco di IE 10 e 11 là fuori: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise (compatibilità in fondo)

Quindi, JavaScript è a thread singolo. Se non si effettua una chiamata asincrona, si comporterà in modo prevedibile. Il thread JavaScript principale eseguirà una funzione completamente prima di eseguire quella successiva, nell'ordine in cui compaiono nel codice. Garantire l'ordine delle funzioni sincrone è banale: ogni funzione verrà eseguita completamente nell'ordine in cui è stata chiamata.

Pensa alla funzione sincrona come a un'unità atomica di lavoro . Il thread JavaScript principale lo eseguirà completamente, nell'ordine in cui le istruzioni compaiono nel codice.

Ma, lancia la chiamata asincrona, come nella seguente situazione:

showLoadingDiv(); // function 1

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

hideLoadingDiv(); // function 3

Questo non fa quello che vuoi . Esegue istantaneamente la funzione 1, la funzione 2 e la funzione 3. Il caricamento di div lampeggia ed è andato, mentre la chiamata ajax non è quasi completa, anche se makeAjaxCall()è tornata. LA COMPLICAZIONE è che makeAjaxCall()ha suddiviso il suo lavoro in blocchi che vengono fatti avanzare gradualmente a ogni giro del thread JavaScript principale - si sta comportando in modo asincrono. Ma quello stesso thread principale, durante uno spin / run, ha eseguito le porzioni sincrone in modo rapido e prevedibile.

Quindi, il modo in cui l'ho gestita : come ho detto, la funzione è l'unità atomica di lavoro. Ho combinato il codice della funzione 1 e 2 - Ho inserito il codice della funzione 1 nella funzione 2, prima della chiamata asincrona. Mi sono liberato della funzione 1. Tutto fino alla chiamata asincrona inclusa, compresa, viene eseguita in modo prevedibile, in ordine.

ALLORA, al termine della chiamata asincrona, dopo diversi giri del thread JavaScript principale, viene chiamata la funzione 3. Ciò garantisce l'ordine . Ad esempio, con ajax, il gestore eventi onreadystatechange viene chiamato più volte. Quando segnala che è stato completato, chiama l'ultima funzione desiderata.

Sono d'accordo che è più disordinato. Mi piace che il codice sia simmetrico, mi piace che le funzioni facciano una cosa (o vicino ad essa) e non mi piace che la chiamata ajax sia in alcun modo responsabile del display (creando una dipendenza dal chiamante). MA, con una chiamata asincrona incorporata in una funzione sincrona, è necessario scendere a compromessi per garantire l'ordine di esecuzione. E devo codificare per IE 10, quindi nessuna promessa.

Riepilogo : per le chiamate sincrone, garantire l'ordine è banale. Ogni funzione viene eseguita completamente nell'ordine in cui è stata chiamata. Per una funzione con una chiamata asincrona, l'unico modo per garantire l'ordine è monitorare il completamento della chiamata asincrona e chiamare la terza funzione quando viene rilevato quello stato.

Per una discussione sui thread JavaScript, consultare: https://medium.com/@francesco_rizzi/javascript-main-thread-dissected-43c85fce7e23 e https://developer.mozilla.org/en-US/docs/Web/JavaScript/ EventLoop

Inoltre, un'altra domanda simile e molto apprezzata su questo argomento: come devo chiamare 3 funzioni per eseguirle una dopo l'altra?


8
Per i downvoter, sarei curioso di sapere cosa c'è che non va in questa risposta.
kermit,

0

Questo è quello che mi è venuto in mente, dal momento che ho bisogno di eseguire diverse operazioni in una catena.

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

Aggiungi almeno un commento di alto livello per la descrizione dello snippet specificato. Questo per spiegare come il tuo codice risolve il problema.
Cold Cerberus,

Il problema era eseguire una seconda funzione dopo la prima. Mostro come eseguire una seconda funzione dopo la prima e anche come eseguire una terza funzione dopo la seconda. Ho creato tre funzioni per rendere più semplice la comprensione del codice richiesto.
Niclas Arnberger,

0

Il tuo divertimento principale chiamerà prima Fun, poi al termine del gioco il tuo prossimo divertimento chiamerà.

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');
            });
        }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.