// js · Web Platform Advent #3
async/await in JavaScript: una guida pratica
Come funzionano async e await, come si rapportano alle promise, la gestione degli errori con try/catch, l'await nei cicli e l'insidia seriale-vs-parallelo — con esempi eseguibili.
Le parole chiave async e await sono sintassi sopra le promise. Ti permettono di scrivere codice asincrono che si legge dall'alto in basso come codice sincrono, pur restando non bloccante dietro le quinte. Se conosci le promise, conosci già async/await — elimina semplicemente il cerimoniale del .then().
Le basi
Contrassegna una funzione come async e potrai usare await al suo interno. await mette in pausa quella funzione finché la promise non si risolve, poi ti restituisce il valore risolto:
async function getUser(id) {
const res = await fetch('/api/user/' + id);
const user = await res.json();
return user.name;
}
getUser(42).then((name) => console.log(name)); Due cose da ricordare: una funzione async restituisce sempre una promise (qualunque cosa restituisci viene avvolta in una), e await funziona solo all'interno di una funzione async — tranne al livello superiore di un modulo ES, dove l'await di primo livello è consentito.
async/await vs promise
Sono lo stesso meccanismo. Questa catena di promise…
fetch('/api/user')
.then((res) => res.json())
.then((user) => console.log(user.name)); …è esattamente equivalente a questa versione async, che la maggior parte delle persone trova più facile da leggere e da debuggare:
const res = await fetch('/api/user');
const user = await res.json();
console.log(user.name); Puoi mescolarle liberamente — fai await di qualsiasi promise, incluso il risultato di Promise.all().
Gestione degli errori con try/catch
Con async/await catturi le promise rifiutate usando il normale try/catch, lo stesso costrutto che usi per gli errori sincroni:
async function loadUser(id) {
try {
const res = await fetch('/api/user/' + id);
if (!res.ok) throw new Error('HTTP ' + res.status);
return await res.json();
} catch (err) {
console.error('Could not load user:', err.message);
return null;
} finally {
console.log('loadUser finished');
}
} Un await rifiutato lancia un'eccezione su quella riga, quindi viene eseguito il blocco catch. Il blocco finally viene eseguito che si sia verificato un errore o meno.
La grande insidia: await seriale vs parallelo
L'errore più comune con async/await è attendere attività indipendenti una dopo l'altra. Così si esegue in serie — la seconda richiesta non parte finché la prima non termina:
// Slow: total time = a + b + c
const a = await fetchA();
const b = await fetchB();
const c = await fetchC(); Se le attività non dipendono l'una dall'altra, avviale tutte prima, poi attendile insieme con Promise.all. Ora procedono in parallelo:
// Fast: total time ≈ the slowest one
const [a, b, c] = await Promise.all([fetchA(), fetchB(), fetchC()]); await nei cicli
Un semplice ciclo for con await elabora gli elementi uno alla volta, il che è corretto quando ogni passo dipende dal precedente o devi evitare di sovraccaricare un server:
for (const id of ids) {
const item = await fetchItem(id); // sequential — one after another
console.log(item);
} Per elaborarli in modo concorrente, mappali in un array di promise e fai await Promise.all. Nota che forEach non attende le callback async — usa invece map + Promise.all:
const items = await Promise.all(ids.map((id) => fetchItem(id))); Riferimento rapido
| Obiettivo | Pattern |
|---|---|
| Attendere una promise | const x = await p; |
| Gestire gli errori | try { await ... } catch (e) { ... } |
| Eseguire attività indipendenti in parallelo | await Promise.all([...]) |
| Ciclo sequenziale | for...of con await |
| Ciclo concorrente | map → Promise.all |
async/await non sostituisce le promise — le rende leggibili. Tieni Promise.all nella tua cassetta degli attrezzi per il parallelismo, ricorri a try/catch per gli errori e fai attenzione all'await seriale accidentale, e avrai il JavaScript asincrono ben sotto controllo.