Una funzione async in TypeScript restituisce sempre una promise: se il suo corpo restituisce una string, il suo tipo è Promise<string>. Al suo interno, await scarta una Promise<T> e ne ricava un T.
Il comportamento a runtime (cosa mette in pausa await, l'event loop) è JavaScript, trattato in async/await in JavaScript. La parte di TypeScript sono i tipi in entrata e in uscita.
Tipi di ritorno delle funzioni async
Il tipo di ritorno dichiarato di una funzione async deve essere Promise<...>, anche se il corpo restituisce il valore semplice. Se lo ometti, TypeScript lo deduce.
Scrivere async function count(): number è l'errore TS1064 (The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<number>'?). Il tipo di utilità Awaited<T> dà il tipo scartato: Awaited<ReturnType<typeof count>> è number.
Dimenticare await
Un await mancante ti lascia in mano una Promise<T> invece di un T. TypeScript ne intercetta la maggior parte perché i tipi non combaciano più:
Il bug di if (ok) è reale: un oggetto promise è sempre truthy, quindi ha concesso l'accesso. TypeScript lo segnala come TS2801. Anche assegnare la promise a una variabile boolean, o leggere una proprietà che la promise non ha, non compilerebbe. Una chiamata di cui ignori il risultato (save(user);) non viene intercettata; se ne occupa la regola di typescript-eslint no-floating-promises.
Gestione degli errori con try/catch
Una promise rifiutata fa lanciare un errore ad await, quindi il normale try/catch funziona. Con strict, la variabile nel catch è unknown, e la restringi prima di leggere .message:
Un throw dentro una funzione async rifiuta la sua promise invece di lanciare l'errore nel punto della chiamata. Altri pattern, tra cui classi di errore personalizzate e restituire risultati invece di lanciare, sono nella pagina sulla gestione degli errori.
await top-level
await fuori da qualsiasi funzione funziona solo in un modulo ES. Un file compilato come CommonJS (il caso di questi esempi, e dei progetti Node senza "type": "module") lo rifiuta:
index.ts(2,14): error TS1309: The current file is a CommonJS module and cannot use 'await' at the top level.
Avvolgi il codice in una funzione main async e chiamala, come fa ogni esempio di questa pagina. In un progetto con moduli ES ("type": "module" in package.json con module impostato a node16 o nodenext, oppure module: "esnext" per un bundler), l'await top-level è consentito.
Sequenziale vs parallelo
Ogni await aspetta la sua promise prima che parta la riga successiva. Per chiamate indipendenti, avviale tutte prima e attendile insieme con Promise.all:
Nella metà sequenziale, b non può finire prima di a perché non è ancora partita. Nella metà parallela, d finisce per prima, e il tempo totale è circa quello della chiamata più lunga invece della somma. Promise.all restituisce comunque i risultati nell'ordine di input, tipizzati come tupla.
La trappola di forEach
forEach ignora la promise restituita da una callback async, quindi nessuno aspetta il lavoro:
for...of con await elabora gli elementi uno alla volta; Promise.all con map li esegue in parallelo e li attende tutti. forEach non fa né l'una né l'altra cosa, e TypeScript non avvisa, perché una callback tipizzata per restituire void ne accetta una che restituisce una promise.
Iterazione asincrona con for await
for await...of scorre un iterabile asincrono, come un generatore async, attendendo ogni valore:
async function* pages(total: number): AsyncGenerator<string[]> {
for (let page = 1; page <= total; page++) {
await new Promise((r) => setTimeout(r, 10));
yield [`item ${page}a`, `item ${page}b`];
}
}
async function main() {
for await (const batch of pages(3)) {
console.log(batch.join(", ")); // batch: string[]
}
}
main();
Il tipo dell'elemento viene dall'annotazione AsyncGenerator<T> del generatore, o dall'inferenza se la ometti.
Domande frequenti
Qual è il tipo di ritorno di una funzione async in TypeScript?
Sempre una promise. Una funzione async che restituisce un number ha tipo di ritorno Promise<number>, e una che non restituisce nulla ha Promise<void>. Scrivere async function f(): number è l'errore TS1064, che suggerisce Promise<number>.
Come si usa await al livello più alto in TypeScript?
L'await top-level funziona solo in un modulo ES, con module impostato a es2022, esnext, system, preserve, oppure node16/node18/node20/nodenext in un file che Node tratta come ESM, e con target a es2017 o superiore. In un file CommonJS è l'errore TS1309. La soluzione portabile è una funzione main async: async function main() { ... } main();.
Come si gestiscono gli errori con async/await in TypeScript?
Avvolgi l'await in try/catch. Con strict, il valore catturato è tipizzato unknown, quindi restringilo prima: if (e instanceof Error) console.log(e.message). Un rifiuto che non viene mai atteso né catturato diventa un unhandled rejection.
Come si eseguono chiamate async in parallelo in TypeScript?
Avvia prima tutte le promise, poi attendile insieme: const [a, b] = await Promise.all([loadA(), loadB()]). Scrivere await loadA(); await loadB(); le esegue una dopo l'altra. Promise.all conserva il tipo di ogni risultato nella tupla risultante.
Perché async non funziona dentro forEach?
forEach chiama la callback e ignora ciò che restituisce, quindi le promise di una callback async non vengono mai attese: il ciclo termina subito e il codice successivo viene eseguito prima che il lavoro sia finito. Usa for...of con await per lavorare uno alla volta, oppure await Promise.all(items.map(async (x) => ...)) per lavorare in parallelo.