Cours JavaScript et TypeScript · Module 4: JavaScript asynchrone

Gestion des erreurs (try...catch)

15 min de lecture
Dans cette leçon8

Dans la gestion du Parc Jurassique, où les dinosaures peuvent s'échapper des enclos, où les systèmes de sécurité peuvent tomber en panne et où les visiteurs peuvent se retrouver en danger, une bonne gestion des erreurs est d'une importance cruciale. En JavaScript, tout comme dans les procédures de sécurité du parc, nous devons être prêts à affronter des situations imprévues et savoir comment y réagir.

Dans cet exercice, nous nous concentrerons sur les stratégies de gestion des erreurs dans le code asynchrone, essentielles pour construire des applications résilientes et fiables.

Pourquoi la gestion des erreurs est-elle importante ?

Imagine que tu construis un système de surveillance des enclos de dinosaures. Que se passe-t-il si un capteur de température cesse de fonctionner ? Ou si la connexion au système de sécurité est interrompue ? Sans une bonne gestion des erreurs, ton application peut « se figer » ou, pire, continuer à fonctionner sur la base de données incorrectes.

Une bonne gestion des erreurs :

  • Empêche les « défaillances en cascade » - une erreur ne devrait pas faire tomber tout le système
  • Fournit des informations de diagnostic utiles - aide à identifier et corriger rapidement les problèmes
  • Permet des actions correctives - nouvelles tentatives automatiques, bascule vers des systèmes de secours
  • Assure une dégradation gracieuse - même quand certaines fonctionnalités sont indisponibles, les systèmes critiques continuent de fonctionner

Types d'erreurs JavaScript

Avant de plonger dans les stratégies de gestion des erreurs, parlons des différents types d'erreurs que tu peux rencontrer en JavaScript :

1. Erreurs de syntaxe (SyntaxError)

Surviennent quand le code contient une syntaxe invalide. Elles sont détectées à la compilation et empêchent le code de s'exécuter.

1// Exemple d'erreur de syntaxe
2function fetchDinosaurData( {  // Parenthèse fermante manquante
3  // corps de la fonction
4}

2. Erreurs de référence (ReferenceError)

Surviennent quand on essaie d'accéder à une variable ou une fonction qui n'existe pas.

1// Exemple d'erreur de référence
2function updateDinosaurStatus(id) {
3  // La variable 'statuses' n'a pas été définie
4  statuses[id] = "Active";
5}

3. Erreurs de type (TypeError)

Surviennent quand une opération est effectuée sur une valeur du mauvais type.

1// Exemple d'erreur de type
2function countDinosaurs(dinosaurList) {
3  // Si dinosaurList n'est pas un tableau, une TypeError se produira
4  return dinosaurList.length;
5}

4. Erreurs de plage (RangeError)

Surviennent quand une valeur numérique ou un paramètre est en dehors de la plage autorisée.

1// Exemple d'erreur de plage
2function setEnclosureTemperature(temperature) {
3  // Si la température est trop élevée, une RangeError est levée
4  if (temperature > 50) {
5    throw new RangeError("La température de l'enclos ne peut pas dépasser 50°C");
6  }
7  // suite du code
8}

5. Erreurs d'URI (URIError)

Surviennent en cas d'utilisation incorrecte des fonctions d'encodage/décodage d'URI.

1// Exemple d'erreur d'URI
2try {
3  // Tentative de décodage d'un URI invalide
4  decodeURIComponent('%');
5} catch (error) {
6  console.error("URI invalide :", error.message);
7}

6. EvalError

Apparaissait historiquement pour les erreurs liées à la fonction eval(), mais on la rencontre rarement en JavaScript moderne.

7. Erreurs personnalisées

Nous pouvons créer et lever nos propres erreurs pour gérer des cas spécifiques à l'application.

1// Exemple d'erreur personnalisée
2class DinosaurEscapeError extends Error {
3  constructor(dinoId, paddockId, message) {
4    super(message || `Le dinosaure n°${dinoId} s'est échappé de l'enclos n°${paddockId} !`);
5    this.name = "DinosaurEscapeError";
6    this.dinoId = dinoId;
7    this.paddockId = paddockId;
8    this.timestamp = new Date();
9  }
10}
11
12// Utilisation
13function checkPaddockStatus(paddockId) {
14  const status = fetchPaddockStatus(paddockId);
15
16  if (status.dinosaur && !status.gatesClosed) {
17    throw new DinosaurEscapeError(
18      status.dinosaur.id,
19      paddockId,
20      `ALERTE ! Le dinosaure ${status.dinosaur.name} s'est échappé de l'enclos n°${paddockId} !`
21    );
22  }
23
24  return status;
25}

Gestion de base des erreurs avec try...catch

Le mécanisme fondamental de gestion des erreurs en JavaScript est le bloc try...catch :

1try {
2  // Code susceptible de lever une erreur
3  const paddockStatus = checkPaddockStatus(12);
4  updateMonitoringPanel(paddockStatus);
5} catch (error) {
6  // Code de gestion de l'erreur
7  console.error("Erreur de vérification de l'enclos :", error.message);
8  activateAlarm("Erreur de surveillance de l'enclos", error);
9} finally {
10  // Ce code s'exécute toujours, qu'une erreur se soit produite ou non
11  writeSystemLog("Vérification de l'enclos 12 terminée");
12}

Dans l'exemple ci-dessus :

  • Le bloc try contient le code susceptible de lever une erreur
  • Le bloc catch s'exécute lorsqu'une erreur survient dans le bloc try
  • Le bloc finally s'exécute toujours, qu'une erreur se soit produite ou non

Propriétés de l'objet erreur

L'objet erreur passé au bloc catch contient de nombreuses propriétés utiles :

1try {
2  throw new Error("Quelque chose a mal tourné");
3} catch (error) {
4  console.error("Nom de l'erreur :", error.name);       // "Error"
5  console.error("Message :", error.message);            // "Quelque chose a mal tourné"
6  console.error("Stack trace :", error.stack);          // Trace de la pile indiquant où l'erreur s'est produite
7}

Vérifier le type d'erreur

Nous voulons souvent réagir différemment selon les types d'erreurs :

1try {
2  const result = performRiskyOperation();
3  displayResult(result);
4} catch (error) {
5  if (error instanceof SyntaxError) {
6    console.error("Erreur de syntaxe :", error.message);
7  } else if (error instanceof ReferenceError) {
8    console.error("Erreur de référence :", error.message);
9  } else if (error instanceof DinosaurEscapeError) {
10    // Déclencher la procédure d'urgence !
11    activateSecurityProtocol(error.dinoId, error.paddockId);
12    evacuateVisitors();
13    notifySecurityTeam(error);
14  } else {
15    console.error("Une erreur inconnue est survenue :", error);
16  }
17}

Lever des erreurs (throw)

Nous pouvons lever des erreurs nous-mêmes avec le mot-clé throw :

1function setEnclosureTemperature(enclosureId, temperature) {
2  // Valider les arguments
3  if (typeof enclosureId !== 'number' || enclosureId <= 0) {
4    throw new TypeError("L'ID de l'enclos doit être un nombre positif");
5  }
6
7  if (temperature < 10 || temperature > 40) {
8    throw new RangeError("La température doit être comprise entre 10°C et 40°C");
9  }
10
11  // Reste du code qui réglera la température
12  return updateEnclosureTemperature(enclosureId, temperature);
13}
14
15// Utilisation
16try {
17  setEnclosureTemperature("invalid-id", 25);
18} catch (error) {
19  if (error instanceof TypeError) {
20    console.error("Type d'argument invalide :", error.message);
21  } else if (error instanceof RangeError) {
22    console.error("Valeur hors plage :", error.message);
23  } else {
24    console.error("Erreur inattendue :", error.message);
25  }
26}

Créer des classes d'erreurs personnalisées

Pour une gestion des erreurs plus avancée, nous pouvons créer des classes d'erreurs personnalisées qui héritent de Error :

1// Hiérarchie d'erreurs pour les systèmes du parc
2class ParkError extends Error {
3  constructor(message) {
4    super(message);
5    this.name = this.constructor.name;
6    // Préserver la stack trace
7    if (Error.captureStackTrace) {
8      Error.captureStackTrace(this, this.constructor);
9    }
10  }
11}
12
13class SecuritySystemError extends ParkError {
14  constructor(message, systemId) {
15    super(message || `Erreur du système de sécurité n°${systemId}`);
16    this.systemId = systemId;
17  }
18}
19
20class FeedingSystemError extends ParkError {
21  constructor(message, paddockId, foodType) {
22    super(message || `Erreur du système de nourrissage dans l'enclos n°${paddockId}`);
23    this.paddockId = paddockId;
24    this.foodType = foodType;
25  }
26}
27
28class VisitorSystemError extends ParkError {
29  constructor(message, areaId, visitorCount) {
30    super(message || `Erreur du système visiteurs dans la zone n°${areaId}`);
31    this.areaId = areaId;
32    this.visitorCount = visitorCount;
33  }
34}
35
36// Utilisation
37function startFeedingSystem(paddockId, foodType) {
38  try {
39    if (!paddockExists(paddockId)) {
40      throw new FeedingSystemError(
41        `Impossible de démarrer le système de nourrissage : l'enclos n°${paddockId} n'existe pas`,
42        paddockId,
43        foodType
44      );
45    }
46
47    // Plus de code...
48
49  } catch (error) {
50    if (error instanceof FeedingSystemError) {
51      // Gérer spécifiquement l'erreur du système de nourrissage
52      notifyFeedingStaff(error);
53      return { success: false, error: error.message, systemType: "feeding" };
54    } else {
55      console.error("Erreur inattendue :", error);
56      return { success: false, error: "Erreur système inconnue", systemType: "unknown" };
57    }
58  }
59}

Gestion des erreurs dans le code asynchrone

La gestion des erreurs dans le code asynchrone peut être plus complexe, surtout lorsqu'on travaille avec des callbacks, des Promises et des fonctions async/await.

Gestion des erreurs dans les callbacks

Dans les anciens styles de programmation asynchrone à base de callbacks, le pattern « error-first » était courant :

1// Style error-first
2function fetchDinosaurData(id, callback) {
3  setTimeout(() => {
4    if (id <= 0) {
5      // Le premier argument est l'erreur (le cas échéant)
6      callback(new Error(`ID de dinosaure invalide : ${id}`), null);
7    } else {
8      // Le premier argument est null (pas d'erreur), le second est le résultat
9      callback(null, { id, name: `Dino-${id}`, species: "Tyrannosaurus" });
10    }
11  }, 1000);
12}
13
14// Utilisation
15fetchDinosaurData(0, (error, dino) => {
16  if (error) {
17    console.error("Échec de la récupération des données du dinosaure :", error.message);
18    return;
19  }
20
21  console.log("Données du dinosaure récupérées :", dino);
22});

Gestion des erreurs dans les Promises

Dans les Promises, on utilise la méthode .catch() pour gérer les erreurs :

1function fetchDinosaurData(id) {
2  return new Promise((resolve, reject) => {
3    setTimeout(() => {
4      if (id <= 0) {
5        reject(new Error(`ID de dinosaure invalide : ${id}`));
6      } else {
7        resolve({ id, name: `Dino-${id}`, species: "Tyrannosaurus" });
8      }
9    }, 1000);
10  });
11}
12
13// Utilisation
14fetchDinosaurData(0)
15  .then(dino => {
16    console.log("Données du dinosaure récupérées :", dino);
17  })
18  .catch(error => {
19    console.error("Échec de la récupération des données du dinosaure :", error.message);
20  });

Les Promises permettent une gestion des erreurs chaînée :

1fetchDinosaurData(42)
2  .then(dino => {
3    return updateDinosaurStatus(dino.id, "active");
4  })
5  .then(status => {
6    console.log("Statut mis à jour :", status);
7    return sendNotification("Dinosaure activé");
8  })
9  .catch(error => {
10    // Ce catch gère les erreurs de toutes les opérations précédentes
11    console.error("Une erreur est survenue dans la chaîne d'opérations :", error.message);
12  });

Gestion des erreurs avec async/await

Les fonctions async/await permettent d'utiliser la syntaxe traditionnelle try...catch même pour du code asynchrone :

1async function activateDinosaur(id) {
2  try {
3    console.log(`Démarrage de la procédure d'activation du dinosaure n°${id}...`);
4
5    const dino = await fetchDinosaurData(id);
6    console.log(`Données du dinosaure récupérées : ${dino.name}`);
7
8    const paddockStatus = await checkPaddockStatus(dino.paddockId);
9    console.log(`État de l'enclos n°${dino.paddockId} : ${paddockStatus.status}`);
10
11    if (!paddockStatus.isSecure) {
12      throw new SecuritySystemError(
13        `Impossible d'activer le dinosaure : l'enclos n°${dino.paddockId} n'est pas sécurisé`,
14        "paddock-security"
15      );
16    }
17
18    const result = await updateDinosaurStatus(id, "active");
19    console.log(`Statut du dinosaure mis à jour : ${result.status}`);
20
21    return {
22      success: true,
23      message: `Dinosaure n°${id} activé avec succès`,
24      dino
25    };
26  } catch (error) {
27    console.error(`Erreur lors de l'activation du dinosaure n°${id} :`, error.message);
28
29    if (error instanceof SecuritySystemError) {
30      await activateSecurityAlarm(error.systemId, error.message);
31    }
32
33    return {
34      success: false,
35      message: `Échec de l'activation du dinosaure n°${id}`,
36      error: error.message
37    };
38  }
39}

Cette syntaxe est bien plus lisible que les chaînes .then().catch() et permet une approche plus naturelle de la gestion des erreurs.

Stratégies avancées de gestion des erreurs

1. Pattern Retry

Pour les opérations réseau ou la communication avec des systèmes externes, il est souvent intéressant de réessayer lorsqu'une erreur temporaire survient :

1async function withRetry(operation, options = {}) {
2  const {
3    maxAttempts = 3,
4    delay = 1000,
5    delayMultiplier = 2,
6    logging = true
7  } = options;
8
9  let lastError;
10
11  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
12    try {
13      if (logging && attempt > 1) {
14        console.log(`Tentative ${attempt}/${maxAttempts}...`);
15      }
16
17      return await operation(attempt);
18    } catch (error) {
19      lastError = error;
20
21      if (logging) {
22        console.warn(`Tentative ${attempt}/${maxAttempts} échouée :`, error.message);
23      }
24
25      if (attempt < maxAttempts) {
26        const waitTime = delay * Math.pow(delayMultiplier, attempt - 1);
27
28        if (logging) {
29          console.log(`Attente de ${waitTime}ms avant la prochaine tentative...`);
30        }
31
32        await new Promise(resolve => setTimeout(resolve, waitTime));
33      }
34    }
35  }
36
37  throw lastError;
38}
39
40// Exemple d'utilisation
41async function fetchSensorNodeData(nodeId) {
42  return withRetry(
43    async (attempt) => {
44      console.log(`Tentative ${attempt} : récupération des données du nœud n°${nodeId}...`);
45      const response = await fetch(`https://sensor-api.jurassic-park.com/node/${nodeId}`);
46
47      if (!response.ok) {
48        throw new Error(`Réponse HTTP : ${response.status}`);
49      }
50
51      return await response.json();
52    },
53    {
54      maxAttempts: 5,
55      delay: 2000,
56      delayMultiplier: 1.5
57    }
58  );
59}

2. Pattern Circuit Breaker

Ce pattern empêche les tentatives répétées d'opérations qui échouent systématiquement :

1class CircuitBreaker {
2  constructor(options = {}) {
3    this.failureThreshold = options.failureThreshold || 3;
4    this.resetTimeout = options.resetTimeout || 30000;
5    this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
6    this.failureCount = 0;
7    this.nextAttempt = Date.now();
8    this.onStateChange = options.onStateChange || (() => {});
9  }
10
11  async execute(fn) {
12    if (this.state === 'OPEN') {
13      if (Date.now() > this.nextAttempt) {
14        this.changeState('HALF_OPEN');
15      } else {
16        throw new Error('Le circuit breaker est OPEN');
17      }
18    }
19
20    try {
21      const result = await fn();
22      this.onSuccess();
23      return result;
24    } catch (error) {
25      this.onFailure(error);
26      throw error;
27    }
28  }
29
30  onSuccess() {
31    this.failureCount = 0;
32    if (this.state === 'HALF_OPEN') {
33      this.changeState('CLOSED');
34    }
35  }
36
37  onFailure(error) {
38    this.failureCount += 1;
39    if (this.state === 'HALF_OPEN' || this.failureCount >= this.failureThreshold) {
40      this.changeState('OPEN');
41    }
42  }
43
44  changeState(state) {
45    this.state = state;
46    this.onStateChange(state);
47    if (state === 'OPEN') {
48      this.nextAttempt = Date.now() + this.resetTimeout;
49    }
50  }
51}
52
53// Exemple d'utilisation
54const breaker = new CircuitBreaker({
55  failureThreshold: 3,
56  resetTimeout: 10000,
57  onStateChange: (newState) => {
58    console.log(`Le circuit breaker a changé d'état pour : ${newState}`);
59    if (newState === 'OPEN') {
60      sendSystemAlert("Le système de capteurs est indisponible");
61    }
62  }
63});
64
65async function fetchSensorData(sensorId) {
66  try {
67    return await breaker.execute(async () => {
68      const response = await fetch(`https://sensor-api.jurassic-park.com/sensor/${sensorId}`);
69      if (!response.ok) {
70        throw new Error(`Erreur HTTP : ${response.status}`);
71      }
72      return response.json();
73    });
74  } catch (error) {
75    if (error.message === 'Le circuit breaker est OPEN') {
76      console.log(`Capteur n°${sensorId} indisponible. Utilisation des données en cache.`);
77      return fetchFromCache(sensorId);
78    }
79    throw error;
80  }
81}

3. Pattern Fallback

Ce pattern fournit une source de données ou une fonctionnalité alternative quand la source principale échoue :

1async function fetchDinosaurDataWithFallback(dinoId) {
2  try {
3    // Essayer de récupérer depuis la base de données principale
4    return await fetchFromPrimaryDatabase(dinoId);
5  } catch (error) {
6    console.warn(`Échec de la récupération depuis la base principale : ${error.message}`);
7
8    try {
9      // Essayer de récupérer depuis la base de secours
10      console.log(`Essai de la base de données de secours...`);
11      return await fetchFromBackupDatabase(dinoId);
12    } catch (fallbackError) {
13      console.warn(`Échec de la récupération depuis la base de secours : ${fallbackError.message}`);
14
15      try {
16        // Essayer de récupérer depuis le cache local
17        console.log(`Essai du cache local...`);
18        return await fetchFromLocalCache(dinoId);
19      } catch (cacheError) {
20        console.error(`Toutes les sources de données ont échoué !`);
21
22        // Retourner des données minimales pour que l'application puisse continuer à fonctionner
23        return {
24          id: dinoId,
25          name: `Dinosaure inconnu n°${dinoId}`,
26          species: "Inconnu",
27          warning: "Données incomplètes - toutes les sources de données ont échoué"
28        };
29      }
30    }
31  }
32}

4. Pattern Bulkhead

Ce pattern isole les ressources dans des pools dédiés afin qu'une défaillance dans une partie du système n'affecte pas les autres :

1class Bulkhead {
2  constructor(options = {}) {
3    this.maxConcurrent = options.maxConcurrent || 10;
4    this.maxQueued = options.maxQueued || 20;
5    this.active = 0;
6    this.queue = [];
7  }
8
9  async execute(fn) {
10    if (this.active >= this.maxConcurrent) {
11      if (this.queue.length >= this.maxQueued) {
12        throw new Error('Débordement du bulkhead : trop de requêtes en file');
13      }
14
15      // Ajouter à la file
16      return new Promise((resolve, reject) => {
17        this.queue.push({ fn, resolve, reject });
18      });
19    }
20
21    this.active += 1;
22
23    try {
24      const result = await fn();
25      return result;
26    } catch (error) {
27      throw error;
28    } finally {
29      this.active -= 1;
30
31      // Traiter les tâches en file si disponibles
32      if (this.queue.length > 0) {
33        const request = this.queue.shift();
34        this.execute(request.fn).then(request.resolve).catch(request.reject);
35      }
36    }
37  }
38}
39
40// Création de bulkheads séparés pour différents sous-systèmes
41const securityBulkhead = new Bulkhead({ maxConcurrent: 5, maxQueued: 10 });
42const feedingBulkhead = new Bulkhead({ maxConcurrent: 3, maxQueued: 5 });
43const monitoringBulkhead = new Bulkhead({ maxConcurrent: 10, maxQueued: 30 });
44
45// Utilisation
46async function monitorPaddock(paddockId) {
47  return monitoringBulkhead.execute(async () => {
48    return await fetchMonitoringData(paddockId);
49  });
50}
51
52async function updateSecurity(paddockId, settings) {
53  return securityBulkhead.execute(async () => {
54    return await updateSecuritySettings(paddockId, settings);
55  });
56}

5. Middleware d'erreurs

Dans les applications Express/Node.js, tu peux implémenter un middleware pour une gestion centralisée des erreurs :

1// Middleware centralisé de gestion des erreurs
2function errorHandler(err, req, res, next) {
3  // Journaliser l'erreur
4  console.error("Une erreur est survenue :", err);
5
6  // Vérifier le type d'erreur
7  if (err instanceof SecuritySystemError) {
8    // Traitement spécial pour les erreurs de sécurité
9    activateSecurityProtocol(err);
10    return res.status(500).json({
11      error: "Erreur du système de sécurité",
12      code: "SECURITY_FAILURE",
13      message: err.message
14    });
15  } else if (err.name === "ValidationError") {
16    // Erreurs de validation
17    return res.status(400).json({
18      error: "Erreur de validation des données",
19      details: err.details,
20      message: err.message
21    });
22  } else if (err.name === "UnauthorizedError") {
23    // Erreurs d'autorisation
24    return res.status(401).json({
25      error: "Non autorisé",
26      message: "Tu n'as pas la permission d'effectuer cette opération"
27    });
28  }
29
30  // Gestion d'erreur par défaut
31  res.status(500).json({
32    error: "Erreur interne du serveur",
33    message: process.env.NODE_ENV === "production" ?
34      "Désolé, quelque chose a mal tourné" : err.message
35  });
36}
37
38// Utilisation dans Express
39app.use(errorHandler);

Bonnes pratiques de gestion des erreurs

  1. Toujours gérer les erreurs - chaque fonction async devrait avoir une forme de gestion des erreurs.

  2. Utiliser des types d'erreurs spécifiques - créer des classes d'erreurs dédiées pour différents types de problèmes.

  3. Fournir des messages utiles - une erreur doit contenir des informations utiles au diagnostic du problème.

  4. Journaliser les erreurs de façon centralisée - utiliser des systèmes de journalisation qui permettent la collecte et l'analyse centralisées des erreurs.

  5. Ne jamais ignorer les erreurs - si tu interceptes une erreur sans avoir besoin de la traiter davantage, journalise-la au minimum.

  6. Préserver la pile d'appels - quand tu lèves de nouvelles erreurs, préserve les piles d'appels d'origine.

1try {
2  await riskyOperation();
3} catch (error) {
4  // Préservation de la pile d'appels d'origine
5  throw new Error(`Échec de l'exécution de l'opération : ${error.message}`, { cause: error });
6}
  1. Gérer les erreurs au bon niveau - toutes les erreurs ne doivent pas être gérées au même endroit.

  2. Utiliser try...finally - utilise le bloc finally pour nettoyer les ressources, qu'une erreur se soit produite ou non.

  3. Ne pas surcharger d'informations - ne montre pas aux utilisateurs des informations d'erreur détaillées qui ne les concernent pas.

  4. Se préparer à l'imprévu - aie toujours un plan de secours pour les erreurs inattendues.

Résumé

Une bonne gestion des erreurs est un élément crucial de la construction d'applications fiables. Dans le contexte du Parc Jurassique, où la sécurité des visiteurs et de précieux spécimens est en jeu, c'est encore plus important.

Une bonne gestion des erreurs :

  • Garantit que le système continuera de fonctionner même en cas de problème
  • Facilite le diagnostic et la correction des problèmes
  • Améliore l'expérience utilisateur
  • Empêche les défaillances en cascade

Souviens-toi que dans le monde du logiciel, tout comme au Parc Jurassique, il vaut toujours mieux « s'attendre à l'imprévu » et être prêt à y faire face.

Code de cette leçon : index.js
1// Module Pattern - Module pattern
2console.log("MODULE PATTERN - PARK MANAGEMENT SYSTEM\n");
3
4// Module Pattern - encapsulation des données et méthodes
5const DinosaurPark = (function() {
6  // Variables et fonctions privées
7  let dinosaurs = [];
8  let capacity = 20;
9  let isOpen = false;
10
11  function validateCapacity() {
12    return dinosaurs.length < capacity;
13  }
14
15  function generateId() {
16    return 'DINO-' + Date.now() + '-' + Math.floor(Math.random() * 1000);
17  }
18
19  // Interface publique (API)
20  return {
21    addDinosaur: function(species, name) {
22      if (!validateCapacity()) {
23        console.log(`Cannot add ${species}. Park is full! (${dinosaurs.length}/${capacity})`);
24        return false;
25      }
26
27      const dinosaur = {
28        id: generateId(),
29        species: species,
30        name: name,
31        addedAt: new Date().toLocaleTimeString()
32      };
33
34      dinosaurs.push(dinosaur);
35      console.log(`Added: ${species} "${name}" (ID: ${dinosaur.id})`);
36      return true;
37    },
38
39    removeDinosaur: function(id) {
40      const index = dinosaurs.findIndex(d => d.id === id);
41
42      if (index !== -1) {
43        const removed = dinosaurs.splice(index, 1)[0];
44        console.log(`Removed: ${removed.species} "${removed.name}"`);
45        return true;
46      }
47
48      console.log(`Aucun dinosaure trouvé avec l'ID: ${id}`);
49      return false;
50    },
51
52    listDinosaurs: function() {
53      console.log(`\n=== DINOSAUR LIST (${dinosaurs.length}/${capacity}) ===`);
54
55      if (dinosaurs.length === 0) {
56        console.log("Park is empty");
57        return;
58      }
59
60      dinosaurs.forEach((dino, index) => {
61        console.log(`${index + 1}. ${dino.species} "${dino.name}" - ID: ${dino.id}`);
62      });
63    },
64
65    openPark: function() {
66      if (dinosaurs.length === 0) {
67        console.log("Cannot open an empty park!");
68        return false;
69      }
70
71      isOpen = true;
72      console.log(`Park open! Welcome visitors!`);
73      return true;
74    },
75
76    closePark: function() {
77      isOpen = false;
78      console.log(`Park closed.`);
79    },
80
81    getStatus: function() {
82      return {
83        isOpen: isOpen,
84        dinosaurCount: dinosaurs.length,
85        capacity: capacity,
86        occupancy: ((dinosaurs.length / capacity) * 100).toFixed(1) + '%'
87      };
88    }
89  };
90})();
91
92// Using the module
93console.log("=== ADDING DINOSAURS ===\n");
94
95DinosaurPark.addDinosaur('T-Rex', 'Rexy');
96DinosaurPark.addDinosaur('Velociraptor', 'Blue');
97DinosaurPark.addDinosaur('Triceratops', 'Trike');
98
99DinosaurPark.listDinosaurs();
100
101console.log("\n=== STATUT DU PARC ===\n");
102console.log(DinosaurPark.getStatus());
103
104console.log("\n=== OUVERTURE DU PARC ===\n");
105DinosaurPark.openPark();
106
107// Attempt to access private variables
108// console.log(dinosaurs); // ERROR! dinosaurs est privé
109// console.log(capacity); // ERROR! capacity est privé

Tu as repéré une erreur dans cette leçon ?

Teste tes connaissances

Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.

  1. 1. Comment gérer une erreur dans une fonction async ?

Tâches pratiques dans le jeu

  • Éditeur de code

    Écris une fonction async avec un bloc try/catch qui tente de récupérer des données depuis un capteur et gère élégamment les erreurs de connexion.

  • Tri vertical

    Ordonne les étapes du cycle de vie d'une Promise :

Articles utiles