Come posso usare async / wait al massimo livello?


185

Ho esaminato async/ awaite dopo aver esaminato diversi articoli, ho deciso di testare le cose da solo. Tuttavia, non riesco a avvolgere la testa perché questo non funziona:

async function main() {  
    var value = await Promise.resolve('Hey there');
    console.log('inside: ' + value);
    return value;
}

var text = main();  
console.log('outside: ' + text);

La console genera il seguente (nodo v8.6.0):

> esterno: [oggetto Promessa]

> dentro: ciao

Perché il messaggio di registro all'interno della funzione viene eseguito successivamente? Pensavo che il motivo async/ awaitfosse stato creato per eseguire un'esecuzione sincrona usando compiti asincroni.

C'è un modo per usare il valore restituito all'interno della funzione senza usare un .then()after main()?


4
No, solo le macchine del tempo possono rendere il codice asincrono sincrono. awaitnon è altro che zucchero per la thensintassi promettente .
Bergi,

Perché main restituisce un valore? In tal caso, probabilmente non è un punto di accesso e deve essere chiamato da un'altra funzione (ad es. IIFE asincrono).
Estus Flask

@estus era solo un nome di funzione rapida mentre stavo testando le cose nel nodo, non necessariamente rappresentativo di un programmamain
Felipe

2
Cordiali saluti, async/awaitfa parte di ES2017, non ES7 (ES2016)
Felix Kling

Risposte:


270

Non riesco a avvolgere la testa perché non funziona.

Perché mainrestituisce una promessa; tutte le asyncfunzioni lo fanno.

Al livello superiore, è necessario:

  1. Utilizzare una asyncfunzione di livello superiore che non rifiuta mai (a meno che non si desideri errori di "rifiuto non gestito"), oppure

  2. Utilizzare thene catch, o

  3. (Prossimamente!) Usa il livello superioreawait , una proposta che ha raggiunto la fase 3 nel processo che consente l'uso di livello superiore awaitin un modulo.

# 1 - asyncFunzione di livello superiore che non rifiuta mai

(async () => {
    try {
        var text = await main();
        console.log(text);
    } catch (e) {
        // Deal with the fact the chain failed
    }
})();

Notare il catch; si deve gestire i rifiuti promessa / eccezioni asincrone, dal momento che nulla sta andando; non hai un chiamante a cui trasferirli. Se preferisci, puoi farlo sul risultato di chiamarlo tramite la catchfunzione (piuttosto che try/ catchsintassi):

(async () => {
    var text = await main();
    console.log(text);
})().catch(e => {
    // Deal with the fact the chain failed
});

... che è un po 'più conciso (mi piace per questo motivo).

O, ovviamente, non gestire gli errori e consentire solo l'errore "rifiuto non gestito".

# 2 - thenecatch

main()
    .then(text => {
        console.log(text);
    })
    .catch(err => {
        // Deal with the fact the chain failed
    });

Il catchgestore verrà chiamato se si verificano errori nella catena o nel thengestore. (Assicurati che il catchgestore non generi errori, poiché non è stato registrato nulla per gestirli.)

O entrambi gli argomenti per then:

main().then(
    text => {
        console.log(text);
    },
    err => {
        // Deal with the fact the chain failed
    }
);

Si noti di nuovo che stiamo registrando un gestore di rifiuto. Ma in questo modulo, assicurati che nessuno dei tuoi thencallback non generi errori, non è registrato nulla per gestirli.

# 3 di primo livello awaitin un modulo

Non è possibile utilizzare awaital livello superiore di uno script non modulo, ma la proposta di livello superioreawait ( Fase 3 ) consente di utilizzarlo al livello superiore di un modulo. È simile all'utilizzo di un asyncwrapper di funzioni di livello superiore (n. 1 sopra) in quanto non si desidera rifiutare il codice di livello superiore (generare un errore) poiché ciò comporterà un errore di rifiuto non gestito. Quindi, a meno che tu non voglia avere quel rifiuto non gestito quando le cose vanno male, come con il n. 1, vorresti racchiudere il tuo codice in un gestore di errori:

// In a module, once the top-level `await` proposal lands
try {
    var text = await main();
    console.log(text);
} catch (e) {
    // Deal with the fact the chain failed
}

Nota che se lo fai, qualsiasi modulo importato dal tuo modulo attenderà fino a quando la promessa che stai awaitrisolvendo; quando awaitviene valutato un modulo che utilizza il livello superiore , sostanzialmente restituisce una promessa al caricatore del modulo (come fa una asyncfunzione), che attende fino a quando tale promessa non viene risolta prima di valutare i corpi di tutti i moduli che dipendono da esso.


Pensarlo come una promessa ora spiega perché la funzione ritorna immediatamente. Ho sperimentato la creazione di una funzione asincrona anonima di alto livello e ottengo risultati che ora hanno senso
Felipe,

2
@Felipe: Sì, async/ awaitsono lo zucchero sintattico attorno alle promesse (il buon tipo di zucchero :-)). Non ci stai solo pensando di restituire una promessa; lo fa davvero. ( Dettagli .)
TJ Crowder,

1
@LukeMcGregor - Ho mostrato entrambi sopra, prima con tutte le asyncopzioni. Per la funzione di livello superiore, posso vederlo in entrambi i modi (principalmente a causa di due livelli di rientro nella asyncversione).
TJ Crowder,

3
@Felipe - Ho aggiornato la risposta ora che la awaitproposta di livello superiore ha raggiunto la fase 3. :-)
TJ Crowder

1
@SurajShrestha - No. Ma non è un problema che non lo sia. :-)
TJ Crowder,

7

Top-Level await è passato alla fase 3, quindi la risposta alla tua domanda Come posso utilizzare asincronizzazione / attesa al livello superiore? è semplicemente aggiungere awaitla chiamata a main():

async function main() {  
    var value = await Promise.resolve('Hey there');
    console.log('inside: ' + value);
    return value;
}

var text = await main();  
console.log('outside: ' + text)

O semplicemente:

const text = await Promise.resolve('Hey there');
console.log('outside: ' + text)

Tieni presente che è ancora disponibile solo in Webpack@v5.0.0-alpha.15 .

Se tu sei usando TypeScript , è arrivato al punto 3.8 .

v8 ha aggiunto il supporto nei moduli.

È anche supportato da Deno (come commentato da Gonzalo-Bahamondez).


Abbastanza bello Abbiamo una tabella di marcia per l'implementazione di un nodo
Felipe,

Non lo so, ma è molto probabile che presto vedremo un'implementazione di TypeScript e Babel. Il team di TypeScript ha una politica di implementazione delle funzionalità del linguaggio stage-3 e un plug-in Babel è generalmente costruito come parte del processo TC39 per testare le proposte. Vedi github.com/Microsoft/TypeScript/issues/…
Taro il

È disponibile anche in deno (solo js, ​​il dattiloscritto non lo supporta ancora github.com/microsoft/TypeScript/issues/25988 ) deno.land vedi deno.news/issues/… .
Gonzalo Bahamondez,


4

La vera soluzione a questo problema è affrontarlo in modo diverso.

Probabilmente il tuo obiettivo è una sorta di inizializzazione che di solito avviene al livello più alto di un'applicazione.

La soluzione è garantire che ci sia sempre una sola istruzione JavaScript al livello più alto dell'applicazione. Se hai solo un'istruzione nella parte superiore della tua applicazione, allora sei libero di usare asincronizzazione / wait in ogni altro punto in qualsiasi luogo (soggetto ovviamente alle normali regole di sintassi)

Detto in altro modo, avvolgi tutto il tuo livello principale in una funzione in modo che non sia più il livello superiore e che risolva la domanda su come eseguire asincronizzazione / attesa al livello superiore di un'applicazione - non è così.

Ecco come dovrebbe apparire il massimo livello dell'applicazione:

import {application} from './server'

application();

1
Hai ragione sul fatto che il mio obiettivo è l'inizializzazione. Cose come connessioni al database, estrazioni di dati ecc. In alcuni casi era necessario ottenere i dati di un utente prima di procedere con il resto dell'applicazione. In sostanza stai proponendo che application()sia asincrono?
Felipe

1
No, sto solo dicendo che se c'è una sola istruzione JavaScript nella radice della tua applicazione, allora il tuo problema è andato - l'istruzione di livello superiore come mostrato non è asincrona. Il problema è che non è possibile utilizzare l'asincronizzazione al livello superiore - non puoi aspettarti di aspettare effettivamente a quel livello - quindi se c'è sempre solo un'istruzione al livello superiore, hai risolto il problema. Il codice asincrono di inizializzazione ora non è disponibile in alcuni codici importati e pertanto asincrono funzionerà perfettamente e sarà possibile inizializzare tutto all'inizio dell'applicazione.
Duke Dougal,

1
CORREZIONE - l'applicazione È una funzione asincrona.
Duke Dougal,

4
Non sono chiaro scusa. Il punto è che normalmente, al livello più alto, una funzione asincrona non attende ... JavaScript passa direttamente alla dichiarazione successiva, quindi non si può essere certi che il codice di inizializzazione sia stato completato. Se c'è solo una singola istruzione nella parte superiore dell'applicazione, ciò non ha importanza.
Duke Dougal,

3

Per fornire ulteriori informazioni sulle risposte attuali:

Il contenuto di a node.js file è attualmente concatenato, in modo simile a una stringa, per formare un corpo di funzione.

Ad esempio se hai un file test.js:

// Amazing test file!
console.log('Test!');

Quindi node.jsconcatenerà segretamente una funzione simile a:

function(require, __dirname, ... a bunch more top-level properties) {
  // Amazing test file!
  console.log('test!');
}

La cosa più importante da notare è che la funzione risultante NON è una funzione asincrona. Quindi non puoi usare il termineawait direttamente al suo interno!

Ma supponiamo che tu debba lavorare con le promesse in questo file, quindi ci sono due metodi possibili:

  1. Non utilizzare await direttamente all'interno della funzione
  2. Non usare await

L'opzione 1 ci richiede di creare un nuovo ambito (e QUESTO ambito può essere async, perché ne abbiamo il controllo):

// Amazing test file!
// Create a new async function (a new scope) and immediately call it!
(async () => {
  await new Promise(...);
  console.log('Test!');
})();

L'opzione 2 ci richiede di utilizzare l'API promessa orientata agli oggetti (il paradigma meno carino ma ugualmente funzionale di lavorare con le promesse)

// Amazing test file!
// Create some sort of promise...
let myPromise = new Promise(...);

// Now use the object-oriented API
myPromise.then(() => console.log('Test!'));

Spero personalmente che, se è praticabile, node.js di default concatena il codice in una asyncfunzione. Ciò eliminerebbe questo mal di testa.


0

Il massimo livello è una caratteristica del prossimo standard EcmaScript. Attualmente, puoi iniziare a usarlo con TypeScript 3.8 (in versione RC in questo momento).

Come installare TypeScript 3.8

Puoi iniziare a usare TypeScript 3.8 installandolo da npm usando il seguente comando:

$ npm install typescript@rc

Al momento, è necessario aggiungere il rctag per installare l'ultima versione di dattiloscritto 3.8.


Ma allora dovrai spiegare come usarlo?
raarts

-2

Poiché main()viene eseguito in modo asincrono, restituisce una promessa. Devi ottenere il risultato nel then()metodo. E poiché anche i then()ritorni promettono, devi chiamare process.exit()per terminare il programma.

main()
   .then(
      (text) => { console.log('outside: ' + text) },
      (err)  => { console.log(err) }
   )
   .then(() => { process.exit() } )

2
Sbagliato. Una volta che tutte le promesse sono state accettate o rifiutate e non c'è più codice in esecuzione nel thread principale, il processo termina da solo.

@Dev: normalmente si desidera passare valori diversi exit()per segnalare se si è verificato un errore.
9000

@ 9000 Sì, ma ciò non viene fatto qui, e poiché un codice di uscita pari a 0 è l'impostazione predefinita, non è necessario includerlo

@ 9000 infatti, il gestore degli errori dovrebbe probabilmente utilizzareprocess.exit(1)
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.