En TypeScript, une fonction async renvoie toujours une promesse : si son corps renvoie une string, son type est Promise<string>. À l'intérieur, await déballe une Promise<T> en T.
Le comportement à l'exécution (ce que await met en pause, la boucle d'événements) relève du JavaScript, présenté dans async/await en JavaScript. La part de TypeScript, ce sont les types en entrée et en sortie.
Types de retour des fonctions async
Le type de retour déclaré d'une fonction async doit être Promise<...>, même si le corps renvoie la valeur brute. TypeScript le déduit si vous l'omettez.
Écrire async function count(): number provoque l'erreur TS1064 (The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<number>'?). Le type utilitaire Awaited<T> donne le type déballé : Awaited<ReturnType<typeof count>> vaut number.
Oublier await
Un await manquant vous laisse avec une Promise<T> au lieu d'un T. TypeScript détecte la plupart de ces oublis, car les types ne correspondent plus :
Le bug du if (ok) est réel : un objet promesse est toujours truthy, donc l'accès était accordé. TypeScript le signale avec TS2801. Affecter la promesse à une variable boolean, ou lire une propriété que la promesse n'a pas, échouerait aussi à la compilation. Un appel dont vous ignorez le résultat (save(user);) n'est pas détecté ; la règle typescript-eslint no-floating-promises couvre ce cas.
Gérer les erreurs avec try/catch
Une promesse rejetée fait lever une exception à await, donc un try/catch ordinaire fonctionne. Sous strict, la variable du catch est unknown, et vous restreignez son type avant de lire .message :
Un throw dans une fonction async rejette sa promesse au lieu de lever l'exception à l'endroit de l'appel. D'autres techniques, dont les classes d'erreur personnalisées et le renvoi de résultats plutôt que les exceptions, sont sur la page gestion des erreurs.
await de premier niveau
Un await en dehors de toute fonction ne fonctionne que dans un module ES. Un fichier compilé en CommonJS (le cas de ces exemples, et des projets Node sans "type": "module") le refuse :
index.ts(2,14): error TS1309: The current file is a CommonJS module and cannot use 'await' at the top level.
Placez le code dans une fonction main async et appelez-la, comme le font tous les exemples de cette page. Dans un projet en modules ES ("type": "module" dans package.json avec module réglé sur node16 ou nodenext, ou module: "esnext" pour un bundler), le await de premier niveau est autorisé.
Séquentiel ou parallèle
Chaque await attend sa promesse avant que la ligne suivante ne commence. Pour des appels indépendants, lancez-les tous d'abord et attendez-les ensemble avec Promise.all :
Dans la partie séquentielle, b ne peut pas finir avant a, puisqu'il n'a pas encore démarré. Dans la partie parallèle, d finit en premier, et la durée totale correspond à peu près à l'appel le plus long plutôt qu'à la somme. Promise.all renvoie quand même les résultats dans l'ordre des entrées, typés comme un tuple.
Le piège de forEach
forEach ignore la promesse que renvoie un callback async, donc rien n'attend la fin du travail :
for...of avec await traite les éléments un par un ; Promise.all avec map les exécute en parallèle et attend qu'ils soient tous terminés. forEach ne fait ni l'un ni l'autre, et TypeScript ne prévient pas, car un callback typé pour renvoyer void accepte un callback qui renvoie une promesse.
Itération asynchrone avec for await
for await...of parcourt un itérable asynchrone, comme un générateur async, en attendant chaque valeur :
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();
Le type des éléments vient de l'annotation AsyncGenerator<T> du générateur, ou de l'inférence si vous l'omettez.
Questions fréquentes
Quel est le type de retour d'une fonction async en TypeScript ?
Toujours une promesse. Une fonction async qui renvoie un number a pour type de retour Promise<number>, et une fonction qui ne renvoie rien a Promise<void>. Écrire async function f(): number provoque l'erreur TS1064, qui propose Promise<number>.
Comment utiliser await au premier niveau en TypeScript ?
Le await de premier niveau ne fonctionne que dans un module ES, avec module réglé sur es2022, esnext, system, preserve, ou node16/node18/node20/nodenext dans un fichier que Node traite comme ESM, et target à es2017 ou plus. Dans un fichier CommonJS, c'est l'erreur TS1309. La solution portable est une fonction main async : async function main() { ... } main();.
Comment gérer les erreurs avec async/await en TypeScript ?
Entourez le await d'un try/catch. Sous strict, la valeur capturée est typée unknown, donc restreignez son type d'abord : if (e instanceof Error) console.log(e.message). Un rejet qui n'est jamais attendu ni capturé devient un rejet non géré.
Comment exécuter des appels async en parallèle en TypeScript ?
Lancez d'abord toutes les promesses, puis attendez-les ensemble : const [a, b] = await Promise.all([loadA(), loadB()]). Écrire await loadA(); await loadB(); les exécute l'une après l'autre. Promise.all conserve le type de chaque résultat dans le tuple obtenu.
Pourquoi async ne fonctionne-t-il pas dans forEach ?
forEach appelle le callback et ignore ce qu'il renvoie, donc les promesses d'un callback async ne sont jamais attendues : la boucle se termine tout de suite et le code qui suit s'exécute avant la fin du travail. Utilisez for...of avec await pour traiter un élément après l'autre, ou await Promise.all(items.map(async (x) => ...)) pour un traitement parallèle.