En TypeScript, une promesse a le type Promise<T>, où T est le type de la valeur avec laquelle elle se résout. Une fonction qui renvoie une promesse le déclare dans son type de retour, et la valeur obtenue avec then ou await a le type T.
Le fonctionnement des promesses (états, file des microtâches, chaînage) relève du JavaScript pur, présenté dans les promesses JavaScript. Cette page porte sur les types.
Typer new Promise et resolve
Quand new Promise est la valeur renvoyée par une fonction dont le type de retour est déclaré, T vient de ce type. Seul, TypeScript ne déduit pas T des appels à resolve, et vous obtenez Promise<unknown>. Passez l'argument de type explicitement :
Deux détails. resolve est typé à partir de T, donc resolve("42") dans une Promise<number> est une erreur de compilation. Et resolve() sans argument n'est autorisé que si T inclut void : dans une Promise<number>, c'est l'erreur TS2794 (Expected 1 arguments, but got 0. Did you forget to include 'void' in your type argument to 'Promise'?).
Le côté reject n'est pas typé. Il n'existe pas de Promise<T, E> : une promesse peut rejeter avec n'importe quelle valeur.
then, catch et finally
Chaque then renvoie une nouvelle promesse typée d'après ce que renvoie son callback. Si le callback renvoie une promesse, TypeScript la déballe, donc vous n'obtenez jamais de Promise<Promise<T>>.
Le paramètre du callback de catch est typé any dans la bibliothèque standard, pas unknown. Rien ne vous empêche d'écrire e.message sur une valeur qui pourrait être une chaîne. Annotez-le en unknown, comme ci-dessus, et restreignez son type avant de l'utiliser. Un try/catch autour de await fait mieux : sous strict, sa variable de capture est déjà unknown (voir la gestion des erreurs).
Promise.all renvoie un tuple
Avec un littéral de tableau de promesses de types différents, Promise.all renvoie une promesse de tuple, où chaque position garde son propre type :
On peut y mêler des valeurs qui ne sont pas des promesses : elles passent telles quelles. Promise.all rejette dès qu'une entrée rejette, et les autres résultats sont perdus. Promise.race se résout ou rejette avec la première promesse terminée et a pour type l'union des entrées ; Promise.any se résout avec le premier succès et ne rejette avec une AggregateError que si toutes les entrées échouent.
Promise.allSettled et son type de résultat
Promise.allSettled attend chaque entrée et ne rejette jamais. Chaque résultat est un PromiseSettledResult<T>, une union que vous restreignez avec le champ status :
r.value n'existe qu'après le test de status, car la variante rejetée n'a pas de value. reason est any, pour la même raison que dans catch. Le filter utilise un prédicat de type pour que le tableau filtré soit typé comme des résultats réussis.
Encapsuler une API à callbacks dans une promesse
Les API plus anciennes renvoient leurs résultats via un callback, souvent dans le style Node (err, result) => void. Encapsulez-les une fois dans une fonction qui renvoie une promesse typée, et le reste du code peut utiliser await :
Dans Node, util.promisify fait cela pour les fonctions qui suivent la convention (err, result), et beaucoup de modules intégrés ont déjà une version à promesses (node:fs/promises, node:timers/promises).
Erreurs courantes
- Renvoyer
Tlà où la signature annoncePromise<T>, ou l'inverse. Une fonction non async déclaréePromise<User>doit renvoyer une promesse ; une fonctionasyncen renvoie une automatiquement. - Oublier de gérer une promesse. Un appel comme
save(user);sansawait,thennicatchcompile sans problème, et un rejet devient un rejet non géré (qui arrête un processus Node par défaut). La règle typescript-eslintno-floating-promisesdétecte ces cas. - Faire confiance au type de
.catch((e) => ...). Soneestany. Annotez-le enunknown. - Entourer de
new Promisequelque chose qui renvoie déjà une promesse. Renvoyez ou attendez simplement la promesse existante avecawait.
Questions fréquentes
Qu'est-ce que Promise<T> en TypeScript ?
Promise<T> est le type d'une promesse qui se résout avec une valeur de type T. Une fonction qui renvoie Promise<string> rend une promesse dont le callback de then, ou await, donne une string. Une promesse qui se résout sans valeur est une Promise<void>.
Comment typer new Promise en TypeScript ?
Passez l'argument de type : new Promise<number>((resolve, reject) => ...). Sans lui, TypeScript ne peut pas déduire la valeur des appels à resolve et le résultat est Promise<unknown>. Pour une promesse qui se résout sans rien, utilisez new Promise<void>(resolve => ...) afin que resolve() sans argument soit accepté.
Quel est le type de l'erreur dans le catch d'une promesse ?
any. Le paramètre reason du callback de catch est typé any dans la bibliothèque standard, parce que n'importe quoi peut être levé ou rejeté. Annotez-le vous-même en unknown (.catch((e: unknown) => ...)) et restreignez-le avec instanceof Error avant de l'utiliser.
Comment Promise.all fonctionne-t-il avec les types en TypeScript ?
Promise.all sur un littéral de tableau renvoie un type tuple avec un élément par entrée, dans l'ordre : Promise.all([getUser(), getCount()]) vaut Promise<[User, number]>, donc la déstructuration donne à chaque valeur son propre type. Sur un tableau d'un même type, T[], il renvoie Promise<T[]>.
Quelle est la différence entre Promise.all et Promise.allSettled ?
Promise.all rejette dès qu'une entrée rejette. Promise.allSettled se résout toujours, avec un tableau d'objets PromiseSettledResult<T> : { status: "fulfilled", value } ou { status: "rejected", reason }. Testez status pour restreindre chacun d'eux.