La gestion des erreurs est un aspect fondamental de la programmation qui garantit que les applications peuvent gérer avec grâce des situations inattendues. En JavaScript, bien que try-catch
soit couramment utilisé, il existe des techniques plus avancées pour améliorer la gestion des erreurs.
Cet article explore ces méthodes avancées, offrant des solutions pratiques pour améliorer vos stratégies de gestion des erreurs et rendre vos applications plus résilientes.
Qu’est-ce que la gestion des erreurs ?
Le but de la gestion des erreurs
La gestion des erreurs anticipe, détecte et répond aux problèmes qui surviennent pendant l’exécution du programme. Une gestion adéquate des erreurs améliore l’expérience utilisateur, maintient la stabilité de l’application et garantit sa fiabilité.
Types d’erreurs en JavaScript
- Erreurs de syntaxe. Ce sont des erreurs dans la syntaxe du code, comme des parenthèses manquantes ou une utilisation incorrecte des mots-clés.
- Erreurs d’exécution. Elles se produisent pendant l’exécution, comme l’accès aux propriétés d’objets indéfinis.
- Erreurs logiques. Ces erreurs ne provoquent pas le plantage du programme mais mènent à des résultats incorrects, souvent en raison d’une logique défaillante ou d’effets secondaires non intentionnels.
Pourquoi le try-catch n’est pas suffisant
Les limites du try-catch
- Limitations de portée. Gère uniquement le code synchrone dans son bloc et n’affecte pas les opérations asynchrones à moins qu’elles ne soient spécifiquement gérées.
- Échecs silencieux. Une utilisation excessive ou incorrecte peut entraîner l’ignorance silencieuse des erreurs, causant potentiellement un comportement inattendu.
- Propagation des erreurs. Ne prend pas en charge nativement la propagation des erreurs à travers différentes couches de l’application.
Quand utiliser try-catch
- Code synchrone. Efficace pour gérer les erreurs dans des opérations synchrones comme l’analyse JSON.
- Sections critiques. À utiliser pour protéger les sections de code critiques où les erreurs peuvent avoir des conséquences graves.
Classes d’erreurs personnalisées : Amélioration des informations d’erreur
Création d’une classe d’erreur personnalisée
Les classes d’erreurs personnalisées étendent la classe intégrée Error
pour fournir des informations supplémentaires:
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = 'ValidationError';
this.field = field;
this.stack = (new Error()).stack; // Capture the stack trace
}
}
Avantages des erreurs personnalisées
- Clarté. Offre des messages d’erreur spécifiques.
- Gestion granulaire. Permet de gérer séparément les types d’erreurs spécifiques.
- Métadonnées d’erreur. Inclut un contexte supplémentaire sur l’erreur.
Utilisations des erreurs personnalisées
- Échecs de validation. Erreurs liées à la validation des saisies utilisateur.
- Erreurs spécifiques au domaine. Erreurs adaptées à des domaines d’application spécifiques comme l’authentification ou le traitement des paiements.
Gestion centralisée des erreurs
Gestion globale des erreurs dans Node.js
Centralisez la gestion des erreurs en utilisant des middlewares :
app.use((err, req, res, next) => {
console.error('Global error handler:', err);
res.status(500).json({ message: 'An error occurred' });
});
Gestion centralisée des erreurs dans les applications frontend
Mettez en œuvre une gestion centralisée des erreurs dans React en utilisant des limites d’erreurs:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.error('Error caught by ErrorBoundary:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
Avantages de la gestion centralisée des erreurs
- Cohérence. Assure une approche uniforme de la gestion des erreurs.
- Maintenance plus facile. Les mises à jour centralisées réduisent le risque de manquer des changements.
- Meilleur journalisation et suivi. Facilite l’intégration avec des outils de suivi.
Propagation des erreurs
Propagation des erreurs dans le code synchrone
Utilisez throw
pour propager les erreurs:
function processData(data) {
try {
validateData(data);
saveData(data);
} catch (error) {
console.error('Failed to process data:', error);
throw error;
}
}
Propagation des erreurs dans le code asynchrone
Gérez les erreurs avec des promesses ou async
/await
:
async function fetchData(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Failed to fetch data:', error);
throw error;
}
}
Quand propager les erreurs
- Erreurs critiques. Propagez les erreurs qui affectent toute l’application.
- Logique métier. Permettez aux composants de niveau supérieur de gérer les erreurs de logique métier.
Gestion des erreurs dans le code asynchrone
Gestion des erreurs avec async/await
Utilisez try-catch
pour gérer les erreurs dans les fonctions asynchrones:
async function fetchUserData(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error('Failed to fetch user data');
}
return await response.json();
} catch (error) {
console.error('Error fetching user data:', error);
return null;
}
}
Utilisation de Promise.all avec gestion des erreurs
Gérez plusieurs promesses et erreurs:
async function fetchMultipleData(urls) {
try {
const responses = await Promise.all(urls.map
(url => fetch(url)));
return await Promise.all(responses.map(response => {
if (!response.ok) {
throw new Error(`Failed to fetch ${response.url}`);
}
return response.json();
}));
} catch (error) {
console.error('Error fetching multiple data:', error);
return [];
}
}
Écueils courants dans la gestion des erreurs asynchrones
- Promesses non capturées. Gérez toujours les promesses en utilisant
await
,.then()
ou.catch()
. - Échecs silencieux. Assurez-vous que les erreurs ne sont pas avalées silencieusement.
- Les conditions de course. Soyez prudent avec les opérations asynchrones.
Enregistrement des erreurs
Enregistrement des erreurs côté client
Capturer les erreurs globales :
window.onerror = function(message, source, lineno, colno, error) {
console.error('Global error captured:', message, source, lineno, colno, error);
sendErrorToService({ message, source, lineno, colno, error });
};
Enregistrement des erreurs côté serveur
Utilisez des outils comme Winston pour l’enregistrement côté serveur :
const winston = require('winston');
const logger = winston.createLogger({
level: 'error',
format: winston.format.json(),
transports: [new winston.transports.File({ filename: 'error.log' })]
});
app.use((err, req, res, next) => {
logger.error(err.stack);
res.status(500).send('An error occurred');
});
Surveillance et alerte
Configurer une surveillance en temps réel et des alertes avec des services comme PagerDuty ou Slack :
function notifyError(error) {
// Send error details to monitoring service
}
Meilleures pratiques pour l’enregistrement des erreurs
- Inclure le contexte. Enregistrer des contextes supplémentaires comme les données de requête et les informations utilisateur.
- Éviter la surcharge des journaux. Enregistrer des informations essentielles pour éviter les problèmes de performance.
- Analyser régulièrement les journaux. Examiner régulièrement les journaux pour détecter et résoudre les problèmes récurrents.
Dégradation gracieuse et solutions de secours
Dégradation gracieuse
Concevez votre application pour continuer à fonctionner avec des capacités réduites :
function renderProfilePicture(user) {
try {
if (!user.profilePicture) {
throw new Error('Profile picture not available');
}
return `<img data-fr-src="${user.profilePicture}" alt="Profile Picture">`;
} catch (error) {
console.error('Error rendering profile picture:', error.message);
return '<img src="/default-profile.png" alt="Default Profile Picture">';
}
}
Mécanismes de secours
Fournir des alternatives en cas d’échec des opérations principales :
async function fetchDataWithFallback(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error('Network response was not ok');
}
return await response.json();
} catch (error) {
console.error('Error fetching data:', error);
return { message: 'Default data' }; // Fallback data
}
}
Mise en œuvre de la dégradation gracieuse
- Solutions de secours pour l’interface utilisateur. Fournir des éléments d’interface utilisateur alternatifs en cas d’échec des fonctionnalités.
- Solutions de secours pour les données. Utiliser des valeurs mises en cache ou par défaut lorsque les données en direct ne sont pas disponibles.
- Mécanismes de réessai. Mettre en œuvre une logique de réessai pour les erreurs transitoires.
Équilibrer la dégradation gracieuse
Équilibrez la mise en place de solutions de secours avec le fait de tenir les utilisateurs informés des problèmes:
function showErrorNotification(message) {
// Notify users about the issue
}
Test de la gestion des erreurs
Test unitaire de la gestion des erreurs
Vérifiez la gestion des erreurs dans les fonctions individuelles:
const { validateUserInput } = require('./validation');
test('throws error for invalid username', () => {
expect(() => {
validateUserInput({ username: 'ab' });
}).toThrow('Username must be at least 3 characters long.');
});
Test d’intégration
Testez la gestion des erreurs à travers différentes couches d’application:
test('fetches data with fallback on error', async () => {
fetch.mockReject(new Error('Network error'));
const data = await fetchDataWithFallback('https://api.example.com/data');
expect(data).toEqual({ message: 'Default data' });
});
Test de bout en bout
Simulez des scénarios réels pour tester la gestion des erreurs:
describe('ErrorBoundary', () => {
it('displays error message on error', () => {
cy.mount(<ErrorBoundary><MyComponent /></ErrorBoundary>);
cy.get(MyComponent).then(component => {
component.simulateError(new Error('Test error'));
});
cy.contains('Something went wrong.').should('be.visible');
});
});
Meilleures pratiques pour tester la gestion des erreurs
- Couvrir les cas limites. Assurez-vous que les tests couvrent divers scénarios d’erreur.
- Tester les solutions de secours. Vérifiez que les mécanismes de secours fonctionnent comme prévu.
- Automatiser les tests. Utilisez des pipelines CI/CD pour automatiser et garantir une gestion robuste des erreurs.
Scénarios Réels
Scénario 1 : Système de Traitement des Paiements
Gérez les erreurs lors du traitement des paiements:
- Classes d’erreur personnalisées. Utilisez des classes telles que
CardValidationError
,PaymentGatewayError
. - Logique de réessai. Mettez en place des réessais pour les problèmes liés au réseau.
- Journalisation centralisée. Surveillez les erreurs de paiement et traitez les problèmes rapidement.
Scénario 2 : Applications à forte intensité de données
Gérez les erreurs dans le traitement des données:
- Dégradation gracieuse. Fournissez des données partielles ou des vues alternatives.
- Données de secours. Utilisez des valeurs mises en cache ou par défaut.
- Journalisation des erreurs. Enregistrez un contexte détaillé pour le dépannage.
Scénario 3 : Authentification et autorisation des utilisateurs
Gérez les erreurs d’authentification et d’autorisation :
- Classes d’erreurs personnalisées. Créez des classes comme
AuthenticationError
,AuthorizationError
. - Gestion centralisée. Enregistrez et surveillez les problèmes liés à l’authentification.
- Dégradation élégante. Offrez des options de connexion alternatives et des messages d’erreur significatifs.
Conclusion
Une gestion avancée des erreurs en JavaScript nécessite d’aller au-delà du simple try-catch
pour adopter des erreurs personnalisées, une gestion centralisée, la propagation et des tests robustes. La mise en œuvre de ces techniques vous permet de construire des applications résilientes qui offrent une expérience utilisateur fluide, même lorsque les choses ne se passent pas comme prévu.
Lectures complémentaires
- « JavaScript : Les bonnes parties » par Douglas Crockford
- « Vous ne connaissez pas JS : Async & Performance » par Kyle Simpson
- MDN Web Docs : Gestion des erreurs
Source:
https://dzone.com/articles/advanced-error-handling-in-javascript-custom-error