Gestion avancée des erreurs en TypeScript : du try/catch aux solutions typées sécurisées
La gestion des erreurs est un aspect critique du développement en TypeScript. Le try/catch de base s'avère souvent insuffisant pour les applications complexes. Dans cet article, nous examinerons des techniques avancées qui aideront à rendre le code plus fiable, prévisible et typé sécurisé.
1. Problème du try/catch standard : perte des types
Le principal inconvénient du catch classique est que la variable d'erreur a le type unknown (ou any dans les anciennes versions). Cela oblige le développeur à vérifier manuellement le type :
try { riskyOperation();} catch (error) { // error: unknown if (error instanceof Error) { console.error(error.message); } else { console.error('Erreur inconnue', error); }}Solution : création d'un utilitaire pour la capture typée des erreurs :
function getErrorMessage(error: unknown): string { if (error instanceof Error) return error.message; if (typeof error === 'string') return error; if (error && typeof error === 'object' && 'message' in error) { return String((error as { message: unknown }).message); } return 'Une erreur inconnue est survenue';}
try { riskyOperation();} catch (error) { console.error(getErrorMessage(error));}2. Classes d'erreurs personnalisées avec données typées
La création d'une hiérarchie d'erreurs permet d'identifier précisément le problème et de transmettre des données supplémentaires :
// Classe de baseclass AppError extends Error { constructor( message: string, public readonly code: number, public readonly isOperational: boolean = true ) { super(message); this.name = 'AppError'; }}
// Erreurs spécialiséesclass ValidationError extends AppError { constructor( message: string, public readonly field: string, public readonly invalidValue: unknown ) { super(message, 400); this.name = 'ValidationError'; }}
class NotFoundError extends AppError { constructor(entity: string, id: string) { super(`${entity} avec l'ID ${id} introuvable`, 404, false); this.name = 'NotFoundError'; }}
// Utilisationfunction findUser(id: string): User { if (!validateId(id)) { throw new ValidationError('ID invalide', 'id', id); } const user = db.findUser(id); if (!user) { throw new NotFoundError('Utilisateur', id); } return user;}
try { const user = findUser('123');} catch (error) { if (error instanceof NotFoundError) { // error.code === 404, error.message contient la description console.warn(error.message); } else if (error instanceof ValidationError) { // error.field et error.invalidValue sont disponibles console.error(`Erreur dans le champ ${error.field} : ${error.message}`); } else if (error instanceof AppError) { console.error(`Erreur d'application (code ${error.code}) : ${error.message}`); }}3. Discriminated Unions pour la gestion des erreurs
Ce modèle permet de traiter les résultats des opérations sans exceptions, en utilisant une union de types avec un discriminant :
// Type de résultat avec discriminanttype Result<T, E = Error> = | { success: true; data: T } | { success: false; error: E };
// Fonction retournant un Resultfunction safeDivide(a: number, b: number): Result<number, string> { if (b === 0) { return { success: false, error: 'Division par zéro' }; } return { success: true, data: a / b };}
// Utilisationconst result = safeDivide(10, 0);
if (result.success) { // TypeScript sait que result.data est un number console.log(`Résultat : ${result.data}`);} else { // result.error est une string console.error(`Erreur : ${result.error}`);}
// Exemple plus complexe avec des erreurs personnaliséestype UserResult = Result<