async/await sono promise travestite
async/await non è un nuovo modello di concorrenza. È zucchero sintattico sopra le promise che ti permette di scrivere codice che sembra sequenziale anche se è asincrono. Stesso meccanismo, forma molto più amichevole.
Ecco lo stesso compito scritto in entrambi i modi:
Entrambe le funzioni restituiscono una promise. Entrambe fanno esattamente la stessa cosa. La versione async si legge dall'alto in basso senza catene di .then: tutto il suo fascino sta lì.
async indica che una funzione restituisce una promise
Metti async davanti a una function, a una arrow function o a un metodo, e succedono due cose:
- La funzione restituisce sempre una promise. Qualunque cosa tu restituisca con
returndiventa il valore risolto. - Puoi usare
awaital suo interno.
Nota che result non è la stringa: è una promise che si risolve nella stringa. Anche se greet non contiene nessun await né lavoro asincrono, la parola chiave async racchiude comunque il valore restituito in una promise. Se la funzione lancia un errore, la promise viene rifiutata.
await aspetta che una promise si concluda
Dentro una funzione async, await somePromise mette in pausa quella funzione finché la promise non si risolve, poi ti consegna il valore risolto. Se la promise viene rifiutata, await lancia un errore.
Osserva l'ordine dell'output. "conto alla rovescia avviato" viene stampato prima di "2", perché await mette in pausa solo la funzione async, non il resto del programma. L'event loop continua a girare; countdown riprende più tardi, quando ogni promise di wait si risolve.
Puoi fare await su qualsiasi cosa simile a una promise. Anche await 42 è valido: i valori che non sono promise vengono racchiusi in Promise.resolve(42) e risolti subito.
Gestire gli errori con try/catch
Con le promise semplici concateni .catch(). Con async/await, una promise rifiutata diventa un'eccezione che puoi catturare nel modo normale:
Un solo try/catch copre ogni await al suo interno. Errori di rete, errori di parsing del JSON e i tuoi throw finiscono tutti nello stesso catch. È un vero passo avanti rispetto alle catene annidate di .then/.catch.
Una cosa a cui fare attenzione: fetch rifiuta solo in caso di errori di rete, non per gli HTTP 4xx/5xx. Controlli tu res.ok e lanci l'errore: è un pattern che vedrai continuamente nel codice reale.
Non usare await in un ciclo se non serve
È la trappola più comune di async/await. Un await sequenziale dentro un ciclo significa che ogni iterazione aspetta la precedente:
sequential impiega circa 900 ms. parallel circa 300 ms. La regola pratica: se le attività non dipendono l'una dal risultato dell'altra, avviale tutte e poi fai await Promise.all. Fai await una alla volta solo quando la chiamata successiva ha davvero bisogno del risultato precedente.
Per le collezioni, l'idioma è Promise.all(items.map(async (x) => ...)). Un semplice for...of con await al suo interno esegue tutto in serie: a volte è quello che vuoi (limiti di frequenza, ordine), ma di solito no.
Mescolare async/await e promise semplici
Non devi scegliere da che parte stare. Le funzioni async restituiscono promise e await funziona su qualsiasi promise, quindi puoi mescolarle liberamente:
I due stili sono intercambiabili. Usa await quando il codice si legge meglio dall'alto in basso; usa .then quando vuoi una soluzione veloce una tantum o sei fuori da un contesto async.
Top-level await (nei moduli ES)
In passato dovevi racchiudere await in una funzione async, perché await non era ammesso al livello principale di uno script. Ora le cose sono cambiate: dentro un modulo ES (un file .mjs o un <script type="module">), puoi usare await direttamente al livello principale:
// in an ES module
const res = await fetch("https://jsonplaceholder.typicode.com/users/1");
const user = await res.json();
console.log(user.name);
Il top-level await ritarda il completamento del modulo finché la promise attesa non si conclude, e anche chi importa quel modulo aspetta. È comodo per caricare configurazioni e per gli import dinamici, ma usalo con parsimonia: un top-level await lento blocca tutti quelli che importano il modulo.
Nei file CommonJS o negli script inline normali, questo fallisce ancora con un SyntaxError. La soluzione classica è una funzione async invocata immediatamente:
Gli errori più comuni
Un giro veloce tra i tranelli tipici:
- Dimenticare
async. Usareawaitin una funzione normale è un errore di sintassi. La soluzione è aggiungereasync, oppure chiamare la funzione async con.then. - Dimenticare di fare
awaitsul risultato.const data = getJSON(url);ti dà una promise, non i dati. Se la usi come se fosse il valore, vedrai comparire[object Promise]nell'output. - Rifiuti non gestiti. Una funzione async che lanci e dimentichi (
doWork();) ingoia gli errori in silenzio, a meno che tu non aggiunga.catcho ci facciaawaitdentro untry/catch. forEachcon callback async.array.forEach(async (x) => await something(x))non aspetta niente:forEachignora le promise restituite. Usafor...ofconawait, oppurePromise.all(array.map(...)).
Eseguilo: "finito?" viene stampato prima di qualsiasi "fatto", perché broken termina senza aspettare. fixed aspetta tutto e poi stampa "finito!" per ultimo.
Quando usare async/await
Usa async/await come scelta predefinita per il codice che esegue più passaggi asincroni in sequenza o che ha bisogno di una gestione degli errori in stile try/catch. Resta sulle promise semplici per le righe singole banali, per il codice di libreria che restituisce una promise senza dover aspettare nulla, o quando ti servono davvero combinatori come Promise.race o .finally() in una catena.
Usato bene, async/await fa leggere il codice asincrono come una ricetta: fai questo, poi questo, poi quest'altro. L'event loop continua a fare il suo lavoro, tu smetti semplicemente di ragionare per callback.
Prossimo passo: la fetch API
Quasi tutti gli esempi qui hanno usato fetch come segnaposto per "qualcosa di asincrono". Merita uno sguardo approfondito: come funzionano richieste e risposte, come gestire il JSON, come impostare gli header e perché fetch non rifiuta in caso di errori HTTP. È l'argomento della prossima pagina.
Domande frequenti
Cosa fa async/await in JavaScript?
async/await è una sintassi per lavorare con le promise che ti permette di scrivere codice asincrono come se fosse sincrono. async indica che una funzione restituisce una promise, e await mette in pausa quella funzione finché una promise non si risolve, poi ti restituisce il valore risolto. Sotto sotto sono sempre promise, solo più facili da leggere.
Posso usare await fuori da una funzione async?
Al livello principale di un modulo ES sì: si chiama top-level await. Dentro funzioni normali o script CommonJS no: await fuori da una funzione async è un errore di sintassi. Di solito la soluzione è racchiudere il codice in una funzione async e chiamarla, oppure trasformare il file in un modulo ES.
Come gestisco gli errori con async/await?
Racchiudi le chiamate con await in un try/catch. Ogni promise rifiutata su cui fai await diventa un'eccezione che il blocco catch può gestire. Per le attività in background su cui non fai await, aggiungi un .catch() alla promise restituita, così i rifiuti non restano non gestiti.
await blocca tutto il programma?
No. await mette in pausa solo la funzione async corrente. L'event loop continua a girare: i timer scattano, le altre attività asincrone vanno avanti e l'interfaccia resta reattiva. Il codice chiamante riceve una promise in sospeso e prosegue subito.