Romain Durand

🎅 Advent of TypeScript 2024 - Jour 14

Le 14 décembre 2024 à 12:34

🎅Santa Hides Behind Perf Review - typage de générateurs asynchrones

https://www.adventofts.com/events/2024/14

Aujourd'hui on s'attaque au typage d'un générateur asynchrone. Encore une des fonctionnalités de JavaScript que je n'utilise jamais. Probablement parce-que j'ai appris le JS à une époque où ça n'existait pas, et que je n'ai pas encore appris à penser les problèmes avec ce prisme.

Donc forcément, si je connais le principe des générateurs asynchrones en JS, je ne sais pas du tout comment on peut typer tout ça. Mais l'avantage de cet exercice, c'est qu'il n'y a pas besoin d'y connaitre grand chose pour en venir à bout !

Solution

Si on regarde dans les tests, on voit déjà une erreur :

type t0_actual = PerfReview<ReturnType<t0_type>>;
Type 'PerfReview' is not generic.2315
Type 'PerfReview' is not generic.

On peut donc changer la définition de PerfReview en :

type PerfReview<T> = unknown;

Maintenant on voit que l'erreur se situe bien au niveau du test. En effet, t0_actual et t0_expected n'ont pour le moment rien à voir.

type t0_type = typeof numberAsyncGenerator;
type t0_type = () => AsyncGenerator<1 | 2 | 3, void, unknown>
type t0_actual = PerfReview<ReturnType<t0_type>>;
type t0_actual = unknown
type t0_expected = 1 | 2 | 3;
type t0_expected = 1 | 2 | 3

Pour ce test, on sait au moins que le T qui sera passé à PerfReview sera ReturnType<t0_type>. Si on l'évalue on voit ça :

type _ = ReturnType<t0_type>
type _ = AsyncGenerator<1 | 2 | 3, void, unknown>

Le problème peut donc être reformulé ainsi :

Si on passe AsyncGenerator<1 | 2 | 3, void, unknown> en paramètre de type à PerfReview, il doit retourner 1 | 2 | 3 (t0_expected)

Tout de suite, c'est bien plus simple, car il suffit d'un infer pour aller extraire ce premier paramètre de type d'AsyncGenerator ! On créé donc un type conditionnel pour pouvoir utiliser le infer en commençant la définition par T extends AsyncGenerator<1 | 2 | 3, void, unknown> ? ..., puis on remplace la valeur qu'on doit extraire par le infer : T extends AsyncGenerator<infer R, void, unknown> ? ..., et enfin on retourne notre type inféré R, sinon on retourne never, comme c'est très souvent le cas dans les types conditionnels.

type PerfReview<T> = T extends AsyncGenerator<infer R, void, unknown> ? R : never;

Et juste avec cette démarche, sans aucun prérequis sur les générateurs asynchrones ou leur types, on a résolu le problème du jour. Ce qui ne veut pas dire qu'il ne faudrait pas aller se renseigner dessus hein, je met ça dans ma TODO 😏

À demain ! 🎄