Données et erreurs
TypeScript 101, Télécom Paris
Pour chaque question ratée : retrouvez sa ligne dans la grille, puis refaites l’exercice sans ouvrir le corrigé.
L’évaluation 2 porte sur tout le cours : ce qui a manqué ici resservira.
| Erreur | À refaire |
|---|---|
find utilisé sans traiter le cas undefined |
2.7, 2.8 |
| Une chaîne de méthodes écrite d’un bloc, sans suivre le type | 2.9 |
reduce sans valeur initiale |
2.8 |
Un tableau d’objets affiché avec ${tableau} |
2.7 |
this oublié dans une méthode |
3.1 |
Un champ laissé public alors qu’un invariant exigeait private |
3.2 |
« Le prix total des livres disponibles à moins de 15 €. »
JSON.parse renvoie any, et s’en protéger<T>JavaScript Object Notation : un format texte d’échange de données, indépendant de tout langage.
Six types de valeurs : chaîne, nombre, booléen, null, tableau, objet. Pas de fonctions, pas de méthodes, pas de Date : des données inertes.
Trois règles qui font trébucher : clés entre guillemets doubles, pas de virgule finale, pas de commentaires.
| Opération | Nom | Sens |
|---|---|---|
JSON.stringify(obj) |
sérialisation | objet TypeScript → chaîne |
JSON.parse(str) |
désérialisation | chaîne → valeur JavaScript |
En sérialisant, vous partez d’une valeur dont TypeScript connaît le type : aucun risque. En désérialisant, vous partez d’une chaîne venue de l’extérieur : TypeScript ne sait rien.
Aucune erreur, aucun avertissement. Les propriétés ont juste disparu.
Le troisième paramètre indente, et c’est très utile pour déboguer : JSON.stringify(obj, null, 2).
anyElle renvoie any : TypeScript renonce à vérifier quoi que ce soit.
C’est exactement le scénario que TypeScript est censé éviter, sauf qu’ici, vous avez ouvert la porte vous-même.
C’est ce que fait 90 % du code TypeScript en production.
Comprenez ce que vous venez de faire : vous avez affirmé que le JSON avait cette forme. Si le serveur change demain, rien ne vous préviendra.
unknown + prédicat de typevalue is ServerData dit au compilateur : « si je renvoie true, tu peux considérer que la valeur a ce type ».
| Approche | Sûreté à l’exécution | Effort | Quand |
|---|---|---|---|
JSON.parse(x) seul |
aucune | nul | jamais |
const u: User = JSON.parse(x) |
aucune | faible | TP, source de confiance |
unknown + prédicat |
réelle | moyen | API externe, production |
En production on utilise Zod, Valibot ou io-ts : ces bibliothèques génèrent le type et le validateur à partir d’une seule déclaration. Le principe reste le même.
JSON.parse restitue des données, jamais des instances de classe.
Une annotation ne crée rien
Vous avez promis une méthode au compilateur ; il vous a cru. Le moteur JavaScript, lui, n’a reçu que des propriétés.
Ne mettez jamais de méthode dans une interface destinée à typer un JSON.parse.
Séparez les deux rôles : une interface de données pour le transport, une classe pour le comportement.
Une fonction qui renvoie le premier élément d’un tableau. Deux mauvaises solutions.
Dupliquer :
Utiliser any :
Or il existe un lien évident : un tableau de number rend un number.
Lisez à voix haute : « pour n’importe quel type T, premier prend un tableau de T et renvoie un T ou undefined ».
Le <T> déclare le paramètre de type, exactement comme les parenthèses déclarent les paramètres de valeur.
Conventions : T pour Type, K pour Key, V pour Value, E pour Element.
Ne précisez <...> que lorsque l’inférence échoue ou produit un type trop large.
class GenericStorage<T> {
private data: Map<string, T> = new Map();
set(key: string, value: T): void { this.data.set(key, value); }
get(key: string): T | undefined { return this.data.get(key); }
}
const nombres = new GenericStorage<number>();
nombres.set("one", 1);
// nombres.set("three", "3"); // ERREUR : string n'est pas numberDeux instances, deux types, une seule classe écrite.
extends<T> seul signifie « n’importe quoi », donc vous ne pouvez rien en faire.
Plus la contrainte est forte, moins le generic est réutilisable, mais plus vous pouvez en faire à l’intérieur.
En C, une fonction qui échoue renvoie un code d’erreur. L’appelant oublie de le tester, et l’erreur se propage silencieusement.
// Style C : le chemin nominal est noyé dans les tests
const a = lire();
if (a === null) return -1;
const b = convertir(a);
if (b === null) return -2;
// Style exception : le chemin nominal est visible d'un coup d'œil
try {
const b = convertir(lire());
} catch (e) { /* tout le traitement d'erreur est ici */ }Le bénéfice principal est la séparation des deux codes.
throwthrow interrompt immédiatement la fonction et remonte la pile d’appels jusqu’à trouver un try. Sans try, le programme s’arrête.
JavaScript permet throw "oups" ou throw 42. Ne le faites jamais : vous perdez le message structuré, le nom et surtout la pile d’appels.
try / catch / finally| Bloc | Rôle | Obligatoire |
|---|---|---|
try |
le code susceptible d’échouer | oui |
catch |
reçoit l’erreur et la traite | l’un des deux |
finally |
s’exécute dans tous les cas | l’un des deux |
finally s’exécute que le try réussisse, échoue, ou même exécute un return. C’est là qu’on libère les ressources.
Contrairement à Java ou C++, il n’y a qu’un seul catch. On distingue les cas soi-même.
catch reçoit un unknownPuisque n’importe quoi peut être levé, TypeScript refuse de supposer.
catch (e: any) compile aussi, et replante le jour où vous cherchez le bug.
Comparer des chaînes de messages est fragile. On crée une classe par type d’erreur.
Tout l’intérêt : une erreur peut porter des données, pas seulement un texte.
instanceoftry {
console.log(this.diviser(a, b));
} catch (e: unknown) {
if (e instanceof ErreurDivisionParZero) {
console.error("Division par zéro !");
} else if (e instanceof ErreurValeurTropGrande) {
console.error(`${e.valeur} est trop grand`); // accès à la donnée
} else if (e instanceof Error) {
console.error("Erreur inattendue :", e.message);
}
}Après le test, TypeScript réduit le type de e et donne accès aux propriétés spécifiques. Ordonnez du plus précis au plus général.
Parcours essentiel, dans l’ordre
any au genericEnsuite