Menu

Promise en TypeScript : Promise<T>, Promise.all et allSettled

Comment les promesses sont typées en TypeScript : le type Promise<T>, typer new Promise et resolve, comment then change le type, pourquoi catch vous donne any, Promise.all avec des résultats en tuple, les types de résultat de Promise.allSettled, et encapsuler une API à callbacks dans une promesse typée.

Cette page contient des éditeurs exécutables - modifiez, exécutez et voyez la sortie instantanément.

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 T là où la signature annonce Promise<T>, ou l'inverse. Une fonction non async déclarée Promise<User> doit renvoyer une promesse ; une fonction async en renvoie une automatiquement.
  • Oublier de gérer une promesse. Un appel comme save(user); sans await, then ni catch compile 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-eslint no-floating-promises détecte ces cas.
  • Faire confiance au type de .catch((e) => ...). Son e est any. Annotez-le en unknown.
  • Entourer de new Promise quelque chose qui renvoie déjà une promesse. Renvoyez ou attendez simplement la promesse existante avec await.

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.

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER