9. Les erreurs et exceptions
TypeScript hérite du système d'exceptions de JavaScript, plutôt sommaire : l'instruction [throw] signale une erreur, et la structure [try / catch / finally] l'intercepte. Les scripts de ce chapitre se trouvent dans le dossier [exceptions] du projet.
9.1. script [excep-01]
Ce script affiche l'heure du moment sous la forme heures:minutes:secondes:millisecondes, à l'aide de la bibliothèque [moment.js] (npm install moment), puis illustre le fonctionnement de try/catch/finally en déclenchant une erreur selon la parité des millisecondes courantes :
| 'use strict';
// package moment
import moment from 'moment';
// principe du try / catch / finally
for (let i = 0; i < 10; i++) {
// date - heure du moment courant
const now = Date.now();
// formatage heure pour avoir les millisecondes
const time = moment(now).format("HH:mm:ss:SSS");
// les millisecondes
const milli = Number(time.substr(time.length - 3));
// affichage
console.log("--------------------itération n° ", i, "à", time);
try {
// nbre aléatoire
const nbre = milli % 2;
if (nbre === 0) {
// lancer un msg d'erreur
throw "erreur";
}
// si on arrive ici c'est qu'il n'y a pas eu d'erreur
console.log("pas d'erreur");
} catch (error) {
// si on arrive ici, c'est qu'il y a eu erreur
console.log("erreur1=", error);
} finally {
// exécuté dans tous les cas erreur ou pas
console.log("finally")
}
}
|
- ligne 4 : import moment from 'moment' — import d'une bibliothèque tierce, exactement comme sprintf-js au chapitre « Les chaînes de caractères » ;
- à chaque tour de boucle, si le nombre de millisecondes courant est pair, une erreur est levée (ligne 21) et interceptée par le catch ; sinon, le message « pas d'erreur » s'affiche ;
- la clause [finally] s'exécute systématiquement, qu'il y ait eu erreur ou non.
Ce script dépend de l'heure exacte d'exécution : le résultat ci-dessous (obtenu par une exécution réelle) sera donc différent à chaque lancement — seul le principe (finally toujours exécuté) est garanti :
npx tsx exceptions/excep-01.ts
Résultat de l'exécution :
| --------------------itération n° 0 à 07:40:11:070
erreur1= erreur
finally
--------------------itération n° 1 à 07:40:11:076
erreur1= erreur
finally
--------------------itération n° 2 à 07:40:11:076
erreur1= erreur
finally
--------------------itération n° 3 à 07:40:11:077
pas d'erreur
finally
--------------------itération n° 4 à 07:40:11:077
pas d'erreur
finally
--------------------itération n° 5 à 07:40:11:077
pas d'erreur
finally
--------------------itération n° 6 à 07:40:11:077
pas d'erreur
finally
--------------------itération n° 7 à 07:40:11:077
pas d'erreur
finally
--------------------itération n° 8 à 07:40:11:077
pas d'erreur
finally
--------------------itération n° 9 à 07:40:11:077
pas d'erreur
finally
|
9.2. script [excep-02]
Ce script montre que [throw] peut lancer n'importe quel type de donnée — chaîne, tableau, objet, instance d'Error — et que cette donnée est récupérée intégralement par la clause catch :
| 'use strict';
// on peut "lancer" (throw) à peu près n'importe quoi pour signaler une erreur
let i = 0;
console.log("--------------------essai n° ", i);
// lancer une chaîne de caractères
try {
throw "msg d'erreur";
} catch (error) {
// il y a eu erreur
console.log("erreur=[", error, "], type=", typeof (error));
}
// lancer un objet littéral
i++;
console.log("--------------------essai n° ", i);
try {
throw [1, 2, 3]
} catch (error) {
// il y a eu erreur
console.log("erreur=[", error, "], type=", typeof (error));
}
// lancer un objet
i++;
console.log("--------------------essai n° ", i);
try {
throw { nom: "hercule", pays: "grèce antique" }
} catch (error) {
// il y a eu erreur
console.log("erreur=[", error, "], type=", typeof (error));
}
// lancer un type Error
i++;
console.log("--------------------essai n° ", i);
try {
throw new Error("erreur de connexion au réseau");
} catch (error) {
// il y a eu erreur
console.log("erreur=[", error, "], type=", typeof (error));
}
// lancer un type Error
i++;
console.log("--------------------essai n° ", i);
try {
throw new Error("erreur de connexion au réseau");
} catch (error: any) {
// il y a eu erreur - le message est dans [error.message]
// [any] car TypeScript type les variables catch en [unknown] par défaut (voir les autres
// blocs try/catch ci-dessus, où il suffit d'afficher [error] sans accéder à une propriété)
console.log("erreur.message=[", error.message, "], type(error)=", typeof (error));
}
|
- [Error] est une classe intégrée dont le constructeur admet un message d'erreur comme 1er paramètre, récupérable dans error.message ;
- TypeScript type par défaut la variable de catch en unknown (voir la remarque de la ligne 47) : accéder à une propriété comme .message exige alors soit une vérification de type (instanceof, voir script suivant), soit une annotation explicite catch (error: any) comme à la ligne 47 ici ;
- il existe d'autres classes que Error pour signaler une erreur : EvalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError.
npx tsx exceptions/excep-02.ts
Résultat de l'exécution :
| --------------------essai n° 0
erreur=[ msg d'erreur ], type= string
--------------------essai n° 1
erreur=[ [ 1, 2, 3 ] ], type= object
--------------------essai n° 2
erreur=[ { nom: 'hercule', pays: 'grèce antique' } ], type= object
--------------------essai n° 3
erreur=[ Error: erreur de connexion au réseau
at exceptions/excep-02.ts:35:9 ], type= object
--------------------essai n° 4
erreur.message=[ erreur de connexion au réseau ], type(error)= object
|
9.3. script [excep-03]
Ce script montre comment différencier, dans un catch, le type précis d'erreur intercepté, grâce à l'opérateur [instanceof] :
| 'use strict';
// package moment
import moment from 'moment';
// différencier l'instance d'Error reçue dans un [catch]
for (let i = 0; i < 10; i++) {
// date - heure du moment courant
const now = Date.now();
// formatage heure pour avoir les millisecondes
const time = moment(now).format("HH:mm:ss:SSS");
// les millisecondes
const milli = Number(time.substr(time.length - 3));
console.log("--------------------itération n° ", i);
try {
// nbre aléatoire
const nbre = milli % 3;
switch (nbre) {
case 0:
throw new ReferenceError("erreur 1");
case 1:
throw new RangeError("erreur 2");
default:
throw new EvalError("erreur 3");
}
} catch (error) {
// il y a eu erreur
if (error instanceof ReferenceError) {
console.log("ReferenceError :", error.message);
} else {
if (error instanceof RangeError) {
console.log("RangeError :", error.message);
}
else {
if (error instanceof EvalError) {
console.log("EvalError :", error.message);
}
}
}
}
}
|
Là encore, le résultat dépend de l'heure exacte d'exécution — chaque lancement donne une répartition différente entre les trois types d'erreurs :
npx tsx exceptions/excep-03.ts
Résultat de l'exécution :
| --------------------itération n° 0
RangeError : erreur 2
--------------------itération n° 1
ReferenceError : erreur 1
--------------------itération n° 2
ReferenceError : erreur 1
--------------------itération n° 3
ReferenceError : erreur 1
--------------------itération n° 4
RangeError : erreur 2
--------------------itération n° 5
RangeError : erreur 2
--------------------itération n° 6
RangeError : erreur 2
--------------------itération n° 7
EvalError : erreur 3
--------------------itération n° 8
EvalError : erreur 3
--------------------itération n° 9
EvalError : erreur 3
|
9.4. script [excep-04]
[NOUVEAU depuis 2019] Ce script présente deux compléments importants apparus avec ECMAScript 2022 : l'option [cause] d'Error, et les classes d'erreurs personnalisées.
| 'use strict';
// ========================================================================
// [NOUVEAU depuis 2019] la propriété [cause] d'une erreur (ECMAScript 2022)
// ========================================================================
// il est fréquent d'attraper une erreur technique (bas niveau) et de la
// re-lancer sous la forme d'une erreur plus explicite (haut niveau), pour
// que le code appelant comprenne mieux le contexte métier.
// AVANT 2022, on perdait la trace de l'erreur d'origine en faisant cela.
function lireConfiguration(): void {
// simule une erreur technique de bas niveau (ex: fichier introuvable)
throw new Error("ENOENT: fichier 'config.json' introuvable");
}
function démarrerApplication(): void {
try {
lireConfiguration();
} catch (erreurTechnique) {
// le 2ième paramètre { cause } permet de rattacher l'erreur d'origine
// à la nouvelle erreur, sans perdre l'information
throw new Error("impossible de démarrer l'application", { cause: erreurTechnique });
}
}
try {
démarrerApplication();
} catch (erreur: any) {
console.log("erreur=", erreur.message);
// erreur.cause donne accès à l'erreur d'origine, très utile pour le débogage
console.log("cause=", erreur.cause?.message);
}
// ------------------------------------------------------------------------
// classes d'erreurs personnalisées (possible depuis ECMAScript 2015 déjà,
// mais très souvent utilisé avec [cause] depuis 2022, donc revu ici)
// ------------------------------------------------------------------------
// on peut créer ses propres types d'erreur en étendant [Error], ce qui permet
// ensuite de les distinguer avec [instanceof], comme pour ReferenceError ou RangeError
// (cf. exceptions/excep-03.js)
class ErreurValidation extends Error {
champEnErreur: string;
constructor(message: string, champEnErreur: string, options?: ErrorOptions) {
// on transmet le message et les [options] (dont un éventuel cause) au parent [Error]
super(message, options);
// le nom par défaut serait "Error" ; on le personnalise
this.name = "ErreurValidation";
// on peut ajouter des informations métier propres à ce type d'erreur
this.champEnErreur = champEnErreur;
}
}
function valider(âge: unknown): boolean {
if (typeof (âge) !== "number") {
throw new ErreurValidation("l'âge doit être un nombre", "âge");
}
if (âge < 0 || âge > 130) {
throw new ErreurValidation("l'âge doit être compris entre 0 et 130", "âge");
}
return true;
}
// des essais avec différentes valeurs
for (const valeur of [25, -5, "trente", 200]) {
try {
valider(valeur);
console.log(`valeur [${valeur}] : validation OK`);
} catch (erreur) {
// on peut distinguer nos erreurs métier des autres erreurs grâce à instanceof
if (erreur instanceof ErreurValidation) {
console.log(`valeur [${valeur}] : erreur de validation sur [${erreur.champEnErreur}] - ${erreur.message}`);
} else {
// une erreur inattendue, non prévue par notre code
throw erreur;
}
}
}
|
- lignes 17-22 : quand on attrape une erreur technique de bas niveau pour la re-lancer sous une forme plus explicite, le 2ᵉ paramètre { cause } de new Error(...) permet de conserver la trace de l'erreur d'origine, sans quoi elle serait perdue (comportement d'avant 2022) ;
- ligne 31 : erreur.cause?.message récupère le message de l'erreur d'origine, très utile pour le débogage ;
- lignes 41-53 : une classe d'erreur personnalisée (ErreurValidation extends Error) permet de créer ses propres types d'erreur métier, distinguables ensuite avec instanceof (ligne 72), exactement comme ReferenceError ou RangeError au script précédent.
npx tsx exceptions/excep-04.ts
Résultat de l'exécution :
| erreur= impossible de démarrer l'application
cause= ENOENT: fichier 'config.json' introuvable
valeur [25] : validation OK
valeur [-5] : erreur de validation sur [âge] - l'âge doit être compris entre 0 et 130
valeur [trente] : erreur de validation sur [âge] - l'âge doit être un nombre
valeur [200] : erreur de validation sur [âge] - l'âge doit être compris entre 0 et 130
|