Cours JavaScript et TypeScript · Module 5: JavaScript avancé

Implémenter une application avec les techniques apprises

16 min de lecture
Dans cette leçon4

Bienvenue dans la phase finale de notre aventure à Jurassic Park ! Dans cet exercice, nous allons combiner toutes les techniques apprises jusqu'ici pour créer une application complète de surveillance et de gestion de l'écosystème de Jurassic Park. C'est notre « grand final » avant de passer à la structuration de projets plus grands.

Objectif du projet

Nous allons créer l'application « JurassicTracker » - un système de surveillance, de suivi et de gestion des dinosaures du parc. Notre application utilisera :

  1. Du code modulaire (modules ES)
  2. Des opérations asynchrones (Promises, async/await)
  3. La gestion des erreurs (try/catch, classes d'erreurs personnalisées)
  4. Des services en arrière-plan (setInterval)
  5. La gestion de l'état de l'application

Structure du projet

Avant de commencer l'implémentation, planifions la structure :

1jurassic-tracker/
2├── index.js               # Fichier principal de l'application
3├── modules/
4│   ├── api.js             # Module de communication avec l'API
5│   ├── dinosaurs.js       # Module de gestion des dinosaures
6│   ├── enclosures.js      # Module de gestion des enclos
7│   ├── security.js        # Module des systèmes de sécurité
8│   ├── monitoring.js      # Module de surveillance du parc
9│   ├── utils.js           # Outils utilitaires
10│   └── errors/            # Répertoire des classes d'erreurs
11│       ├── index.js       # Exporte toutes les erreurs
12│       ├── APIError.js    # Erreurs liées à l'API
13│       ├── DinoError.js   # Erreurs liées aux dinosaures
14│       └── SecurityError.js # Erreurs liées à la sécurité
15└── data/
16    └── config.js          # Configuration de l'application

Implémentations des modules

Implémentons chacun des modules. Souviens-toi qu'en pratique, ces fichiers vivraient dans des fichiers séparés selon la structure ci-dessus.

1. Module Erreurs (errors/index.js)

Définissons d'abord nos classes d'erreurs personnalisées :

1// errors/APIError.js
2export class APIError extends Error {
3  constructor(message, statusCode, endpoint) {
4    super(message);
5    this.name = 'APIError';
6    this.statusCode = statusCode;
7    this.endpoint = endpoint;
8    this.timestamp = new Date();
9  }
10
11  isServerError() {
12    return this.statusCode >= 500;
13  }
14
15  isClientError() {
16    return this.statusCode >= 400 && this.statusCode < 500;
17  }
18}
19
20// errors/DinoError.js
21export class DinoError extends Error {
22  constructor(message, dinoId, species) {
23    super(message);
24    this.name = 'DinoError';
25    this.dinoId = dinoId;
26    this.species = species;
27    this.timestamp = new Date();
28  }
29}
30
31export class DinoNotFoundError extends DinoError {
32  constructor(dinoId) {
33    super(`Le dinosaure avec l'ID ${dinoId} est introuvable`, dinoId, null);
34    this.name = 'DinoNotFoundError';
35  }
36}
37
38export class DinoBehaviorError extends DinoError {
39  constructor(message, dinoId, species, behaviorType, dangerLevel) {
40    super(message, dinoId, species);
41    this.name = 'DinoBehaviorError';
42    this.behaviorType = behaviorType;
43    this.dangerLevel = dangerLevel; // 1-10
44  }
45
46  requiresIntervention() {
47    return this.dangerLevel >= 7;
48  }
49}
50
51// errors/SecurityError.js
52export class SecurityError extends Error {
53  constructor(message, sector, systemType) {
54    super(message);
55    this.name = 'SecurityError';
56    this.sector = sector;
57    this.systemType = systemType;
58    this.timestamp = new Date();
59  }
60}
61
62export class EnclosureBreachError extends SecurityError {
63  constructor(sector, dinoSpecies, count) {
64    super(`Brèche d'enclos dans le secteur ${sector} : ${count} spécimens de ${dinoSpecies}`, sector, 'enclosure');
65    this.name = 'EnclosureBreachError';
66    this.dinoSpecies = dinoSpecies;
67    this.count = count;
68  }
69}
70
71// errors/index.js
72export * from './APIError.js';
73export * from './DinoError.js';
74export * from './SecurityError.js';

2. Module API (api.js)

Notre module API gère la communication avec le serveur (hypothétique) de Jurassic Park :

1// modules/api.js
2import { APIError } from '../errors/index.js';
3
4const API_DELAY = 500; // ms - simule le délai réseau
5const API_ERROR_RATE = 0.1; // 10% de chance d'erreur API pour le réalisme
6
7// Base de données de dinosaures simulée
8let dinosaursData = [
9  { id: 'trx1', name: 'Rex', species: 'Tyrannosaurus', enclosureId: 'enc1', health: 98, lastFed: new Date(Date.now() - 3600000), behavior: 'calm' },
10  { id: 'rap1', name: 'Blue', species: 'Velociraptor', enclosureId: 'enc2', health: 95, lastFed: new Date(Date.now() - 7200000), behavior: 'agitated' },
11  { id: 'rap2', name: 'Delta', species: 'Velociraptor', enclosureId: 'enc2', health: 92, lastFed: new Date(Date.now() - 7200000), behavior: 'hunting' },
12  { id: 'tri1', name: 'Sarah', species: 'Triceratops', enclosureId: 'enc3', health: 88, lastFed: new Date(Date.now() - 1800000), behavior: 'sleeping' },
13  { id: 'bro1', name: 'Bronty', species: 'Brontosaurus', enclosureId: 'enc4', health: 100, lastFed: new Date(Date.now() - 900000), behavior: 'grazing' },
14];
15
16// Fonction utilitaire simulant le délai réseau
17const simulateNetworkDelay = () => {
18  return new Promise(resolve => setTimeout(resolve, API_DELAY * (0.5 + Math.random())));
19};
20
21// Fonction utilitaire simulant les erreurs API
22const simulateApiError = (endpoint) => {
23  if (Math.random() < API_ERROR_RATE) {
24    const errorTypes = [
25      { statusCode: 500, message: 'Internal Server Error' },
26      { statusCode: 503, message: 'Service Unavailable' },
27      { statusCode: 429, message: 'Too Many Requests' },
28      { statusCode: 403, message: 'Forbidden - Insufficient permissions' }
29    ];
30    const errorType = errorTypes[Math.floor(Math.random() * errorTypes.length)];
31    throw new APIError(`Erreur API : ${errorType.message}`, errorType.statusCode, endpoint);
32  }
33};
34
35// API de récupération des données des dinosaures
36export async function getDinosaurs() {
37  const endpoint = '/api/dinosaurs';
38  try {
39    await simulateNetworkDelay();
40    simulateApiError(endpoint);
41    return [...dinosaursData]; // Renvoie une copie pour éviter la mutation
42  } catch (error) {
43    if (error instanceof APIError) throw error;
44    throw new APIError('Erreur de récupération des données des dinosaures', 500, endpoint);
45  }
46}
47
48export async function getDinosaurById(id) {
49  const endpoint = `/api/dinosaurs/${id}`;
50  try {
51    await simulateNetworkDelay();
52    simulateApiError(endpoint);
53    const dino = dinosaursData.find(d => d.id === id);
54    if (!dino) {
55      throw new APIError(`Dinosaure avec l'ID ${id} introuvable`, 404, endpoint);
56    }
57    return { ...dino };
58  } catch (error) {
59    if (error instanceof APIError) throw error;
60    throw new APIError('Erreur de récupération du dinosaure', 500, endpoint);
61  }
62}

3. Module Dinosaures (dinosaurs.js)

Ce module gère les données et le comportement des dinosaures :

1// modules/dinosaurs.js
2import { getDinosaurs, getDinosaurById, updateDinosaur, logEvent } from './api.js';
3import { DinoNotFoundError, DinoBehaviorError } from '../errors/index.js';
4
5// Évalue le risque comportemental d'un dinosaure
6function assessBehaviorRisk(dino) {
7  const dangerousSpecies = ['Tyrannosaurus', 'Velociraptor', 'Spinosaurus'];
8  const riskBehaviors = {
9    'calm': 1,
10    'grazing': 1,
11    'sleeping': 2,
12    'exploring': 4,
13    'agitated': 6,
14    'hunting': 7,
15    'aggressive': 9
16  };
17
18  // Le risque de base dépend de l'espèce
19  let baseRisk = dangerousSpecies.includes(dino.species) ? 5 : 2;
20
21  // Modification du risque selon le comportement
22  const behaviorRisk = riskBehaviors[dino.behavior] || 3;
23
24  // Modification du risque selon le temps écoulé depuis le dernier nourrissage
25  const hoursSinceLastFed = (Date.now() - new Date(dino.lastFed).getTime()) / 3600000;
26  const hungerRisk = Math.min(5, Math.floor(hoursSinceLastFed / 3));
27
28  // Niveau de risque total (1-10)
29  const totalRisk = Math.min(10, baseRisk + behaviorRisk + hungerRisk);
30
31  return {
32    risk: totalRisk,
33    factors: {
34      species: baseRisk,
35      behavior: behaviorRisk,
36      hunger: hungerRisk
37    }
38  };
39}
40
41// Récupère tous les dinosaures
42export async function getAllDinosaurs() {
43  try {
44    const dinosaurs = await getDinosaurs();
45
46    // Enrichit les données avec l'évaluation du risque
47    return dinosaurs.map(dino => ({
48      ...dino,
49      riskAssessment: assessBehaviorRisk(dino)
50    }));
51  } catch (error) {
52    console.error('Erreur de récupération de la liste des dinosaures :', error);
53    throw error;
54  }
55}
56
57// Récupère un dinosaure précis par son ID
58export async function getDinosaur(id) {
59  try {
60    const dino = await getDinosaurById(id);
61    return {
62      ...dino,
63      riskAssessment: assessBehaviorRisk(dino)
64    };
65  } catch (error) {
66    if (error.statusCode === 404) {
67      throw new DinoNotFoundError(id);
68    }
69    console.error(`Erreur de récupération du dinosaure ${id} :`, error);
70    throw error;
71  }
72}
73
74// Nourrit un dinosaure
75export async function feedDinosaur(id) {
76  try {
77    const dino = await getDinosaur(id);
78
79    // Met à jour l'état du dinosaure
80    const updatedDino = await updateDinosaur(id, {
81      lastFed: new Date(),
82      health: Math.min(100, dino.health + 5),
83      behavior: 'calm' // Les dinosaures sont plus calmes après avoir été nourris
84    });
85
86    // Journalise l'événement
87    await logEvent('FEEDING', {
88      dinoId: id,
89      species: dino.species,
90      name: dino.name,
91      previousFeedingTime: dino.lastFed
92    });
93
94    return {
95      ...updatedDino,
96      riskAssessment: assessBehaviorRisk(updatedDino)
97    };
98  } catch (error) {
99    console.error(`Erreur lors du nourrissage du dinosaure ${id} :`, error);
100    throw error;
101  }
102}
103
104// Surveille le comportement d'un dinosaure
105export async function monitorBehavior(id) {
106  try {
107    const dino = await getDinosaur(id);
108    const { risk, factors } = assessBehaviorRisk(dino);
109
110    // Vérifie si le comportement nécessite une intervention
111    if (risk >= 8) {
112      const error = new DinoBehaviorError(
113        `Comportement dangereux du dinosaure ${dino.name}`,
114        id,
115        dino.species,
116        dino.behavior,
117        risk
118      );
119
120      // Journalise l'événement à haut risque
121      await logEvent('HIGH_RISK_BEHAVIOR', {
122        dinoId: id,
123        species: dino.species,
124        name: dino.name,
125        behavior: dino.behavior,
126        risk,
127        factors
128      });
129
130      throw error;
131    }
132
133    // Pour un risque moyen, on journalise simplement
134    if (risk >= 5) {
135      await logEvent('MEDIUM_RISK_BEHAVIOR', {
136        dinoId: id,
137        species: dino.species,
138        name: dino.name,
139        behavior: dino.behavior,
140        risk,
141        factors
142      });
143    }
144
145    return { dino, risk, factors };
146  } catch (error) {
147    if (error instanceof DinoBehaviorError) {
148      throw error; // Relance notre erreur personnalisée
149    }
150    console.error(`Erreur de surveillance du dinosaure ${id} :`, error);
151    throw error;
152  }
153}
154
155// Simule un changement de comportement (utilisé par le module de surveillance)
156export async function simulateBehaviorChange() {
157  try {
158    const allDinos = await getAllDinosaurs();
159    const randomIndex = Math.floor(Math.random() * allDinos.length);
160    const targetDino = allDinos[randomIndex];
161
162    // Comportements possibles selon l'espèce et le dernier nourrissage
163    const carnivores = ['Tyrannosaurus', 'Velociraptor', 'Spinosaurus'];
164
165    let behaviorPool;
166    if (carnivores.includes(targetDino.species)) {
167      // Les prédateurs ont plus de chances d'avoir un comportement agressif
168      behaviorPool = ['calm', 'sleeping', 'exploring', 'agitated', 'hunting', 'aggressive'];
169    } else {
170      // Les herbivores sont généralement plus calmes
171      behaviorPool = ['calm', 'grazing', 'sleeping', 'exploring', 'agitated'];
172    }
173
174    // Les dinosaures affamés sont plus agressifs
175    const hoursSinceLastFed = (Date.now() - new Date(targetDino.lastFed).getTime()) / 3600000;
176    if (hoursSinceLastFed > 8) {
177      if (carnivores.includes(targetDino.species)) {
178        behaviorPool = ['agitated', 'hunting', 'aggressive'];
179      } else {
180        behaviorPool = ['agitated', 'exploring'];
181      }
182    }
183
184    // Sélection aléatoire du comportement
185    const newBehavior = behaviorPool[Math.floor(Math.random() * behaviorPool.length)];
186
187    // Met à jour l'état du dinosaure
188    const updatedDino = await updateDinosaur(targetDino.id, {
189      behavior: newBehavior
190    });
191
192    await logEvent('BEHAVIOR_CHANGE', {
193      dinoId: targetDino.id,
194      species: targetDino.species,
195      name: targetDino.name,
196      previousBehavior: targetDino.behavior,
197      newBehavior
198    });
199
200    return {
201      ...updatedDino,
202      riskAssessment: assessBehaviorRisk(updatedDino)
203    };
204  } catch (error) {
205    console.error('Erreur de simulation du changement de comportement :', error);
206    throw error;
207  }
208}

4. Module Enclos (enclosures.js)

Ce module gère les enclos et leur état de sécurité :

1// modules/enclosures.js
2import { getEnclosures, getEnclosureById, updateEnclosure, logEvent } from './api.js';
3import { SecurityError, EnclosureBreachError } from '../errors/index.js';
4import { getAllDinosaurs } from './dinosaurs.js';
5
6// Évalue l'état de sécurité d'un enclos
7function assessSecurityStatus(enclosure, dinosaurs) {
8  // Vérifie combien de dinosaures se trouvent dans l'enclos
9  const enclosureDinosaurs = dinosaurs.filter(dino => dino.enclosureId === enclosure.id);
10
11  // Vérifie la présence de carnivores
12  const carnivores = enclosureDinosaurs.filter(dino =>
13    ['Tyrannosaurus', 'Velociraptor', 'Spinosaurus'].includes(dino.species)
14  );
15
16  // Vérifie le risque total
17  const totalRisk = enclosureDinosaurs.reduce((sum, dino) =>
18    sum + (dino.riskAssessment ? dino.riskAssessment.risk : 3), 0);
19
20  // État de l'alimentation
21  const powerStatus = enclosure.powerStatus === 'active' ? 10 :
22                       enclosure.powerStatus === 'backup' ? 5 : 0;
23
24  // Temps écoulé depuis la dernière inspection (en jours)
25  const daysSinceInspection = (Date.now() - new Date(enclosure.lastInspection).getTime()) / (24 * 3600000);
26  const inspectionScore = daysSinceInspection < 1 ? 10 :
27                          daysSinceInspection < 3 ? 7 :
28                          daysSinceInspection < 7 ? 4 : 1;
29
30  // Calcule le score de sécurité global (0-100)
31  const securityScore = Math.max(0, Math.min(100,
32    enclosure.securityLevel * 10 - totalRisk + powerStatus + inspectionScore
33  ));
34
35  return {
36    id: enclosure.id,
37    name: enclosure.name,
38    sector: enclosure.sector,
39    dinosaurCount: enclosureDinosaurs.length,
40    carnivoreCount: carnivores.length,
41    securityLevel: enclosure.securityLevel,
42    powerStatus: enclosure.powerStatus,
43    securityScore,
44    status: securityScore >= 80 ? 'secure' :
45            securityScore >= 60 ? 'normal' :
46            securityScore >= 40 ? 'warning' : 'danger',
47    dinosaurs: enclosureDinosaurs.map(d => ({
48      id: d.id,
49      name: d.name,
50      species: d.species,
51      risk: d.riskAssessment ? d.riskAssessment.risk : 3
52    }))
53  };
54}
55
56// Récupère tous les enclos avec évaluation de sécurité
57export async function getAllEnclosures() {
58  try {
59    const [enclosures, dinosaurs] = await Promise.all([
60      getEnclosures(),
61      getAllDinosaurs()
62    ]);
63
64    // Enrichit les données avec l'évaluation de sécurité
65    return enclosures.map(enclosure =>
66      assessSecurityStatus(enclosure, dinosaurs)
67    );
68  } catch (error) {
69    console.error('Erreur de récupération de la liste des enclos :', error);
70    throw error;
71  }
72}
73
74// Inspecte un enclos
75export async function inspectEnclosure(id) {
76  try {
77    const enclosure = await getEnclosureById(id);
78
79    // Met à jour la date de la dernière inspection
80    const updatedEnclosure = await updateEnclosure(id, {
81      lastInspection: new Date()
82    });
83
84    // Journalise l'événement
85    await logEvent('ENCLOSURE_INSPECTION', {
86      enclosureId: id,
87      name: enclosure.name,
88      sector: enclosure.sector,
89      previousInspection: enclosure.lastInspection
90    });
91
92    // Récupère les dinosaures pour mettre à jour l'évaluation de sécurité
93    const dinosaurs = await getAllDinosaurs();
94
95    return assessSecurityStatus(updatedEnclosure, dinosaurs);
96  } catch (error) {
97    console.error(`Erreur d'inspection de l'enclos ${id} :`, error);
98    throw error;
99  }
100}
101
102// Simule une panne de courant
103export async function simulatePowerFailure() {
104  try {
105    const enclosures = await getEnclosures();
106
107    // Sélectionne un enclos au hasard
108    const randomIndex = Math.floor(Math.random() * enclosures.length);
109    const targetEnclosure = enclosures[randomIndex];
110
111    if (targetEnclosure.powerStatus !== 'active') return;
112
113    // 70% de chance d'alimentation de secours, 30% de chance de panne totale
114    const newPowerStatus = Math.random() < 0.7 ? 'backup' : 'failure';
115
116    // Met à jour l'état de l'enclos
117    const updatedEnclosure = await updateEnclosure(targetEnclosure.id, {
118      powerStatus: newPowerStatus
119    });
120
121    await logEvent('POWER_FAILURE', {
122      enclosureId: targetEnclosure.id,
123      name: targetEnclosure.name,
124      sector: targetEnclosure.sector,
125      powerStatus: newPowerStatus
126    });
127
128    // Vérifie si la panne pourrait entraîner une brèche d'enclos
129    if (newPowerStatus === 'failure') {
130      const dinosaurs = await getAllDinosaurs();
131      const enclosureDinos = dinosaurs.filter(dino => dino.enclosureId === targetEnclosure.id);
132
133      const dangerousSpecies = ['Tyrannosaurus', 'Velociraptor', 'Spinosaurus'];
134      const hasDangerousDinos = enclosureDinos.some(dino =>
135        dangerousSpecies.includes(dino.species)
136      );
137
138      if (hasDangerousDinos && targetEnclosure.securityLevel >= 8) {
139        const breachingSpecies = enclosureDinos.find(dino =>
140          dangerousSpecies.includes(dino.species)
141        ).species;
142
143        const breachingCount = enclosureDinos.filter(dino =>
144          dino.species === breachingSpecies
145        ).length;
146
147        throw new EnclosureBreachError(
148          targetEnclosure.sector,
149          breachingSpecies,
150          breachingCount
151        );
152      }
153    }
154
155    const dinosaurs = await getAllDinosaurs();
156    return assessSecurityStatus(updatedEnclosure, dinosaurs);
157  } catch (error) {
158    if (error instanceof EnclosureBreachError) {
159      await logEvent('ENCLOSURE_BREACH', {
160        sector: error.sector,
161        species: error.dinoSpecies,
162        count: error.count,
163        timestamp: error.timestamp
164      });
165      throw error;
166    }
167
168    console.error('Erreur de simulation de la panne de courant :', error);
169    throw error;
170  }
171}
172
173// Répare le système d'alimentation
174export async function repairPowerSystem(id) {
175  try {
176    const enclosure = await getEnclosureById(id);
177
178    if (enclosure.powerStatus === 'active') {
179      throw new SecurityError(
180        "Le système d'alimentation est déjà actif",
181        enclosure.sector,
182        'power'
183      );
184    }
185
186    // Met à jour l'état de l'alimentation
187    const updatedEnclosure = await updateEnclosure(id, {
188      powerStatus: 'active'
189    });
190
191    await logEvent('POWER_RESTORED', {
192      enclosureId: id,
193      name: enclosure.name,
194      sector: enclosure.sector,
195      previousStatus: enclosure.powerStatus
196    });
197
198    const dinosaurs = await getAllDinosaurs();
199
200    return assessSecurityStatus(updatedEnclosure, dinosaurs);
201  } catch (error) {
202    console.error(`Erreur de réparation du système d'alimentation de l'enclos ${id} :`, error);
203    throw error;
204  }
205}

5. Fonctions utilitaires (utils.js)

1// modules/utils.js
2export function formatDate(date) {
3  return new Intl.DateTimeFormat('en-US', {
4    day: '2-digit', month: '2-digit', year: 'numeric',
5    hour: '2-digit', minute: '2-digit'
6  }).format(new Date(date));
7}
8
9export function formatTimeAgo(date) {
10  const diff = Date.now() - new Date(date).getTime();
11  const minutes = Math.floor(diff / 60000);
12  const hours = Math.floor(diff / 3600000);
13
14  if (minutes < 1) return "à l'instant";
15  if (minutes < 60) return `il y a ${minutes} min`;
16  if (hours < 24) return `il y a ${hours} h`;
17  return `il y a ${Math.floor(hours / 24)} jours`;
18}
19
20export async function retry(fn, maxAttempts = 3, delay = 1000) {
21  let lastError;
22  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
23    try {
24      return await fn();
25    } catch (error) {
26      lastError = error;
27      if (attempt < maxAttempts) {
28        console.log(`Tentative ${attempt} échouée, nouvelle tentative dans ${delay}ms...`);
29        await new Promise(resolve => setTimeout(resolve, delay));
30      }
31    }
32  }
33  throw lastError;
34}
35
36export function memoize(fn) {
37  const cache = new Map();
38  return async function(...args) {
39    const key = JSON.stringify(args);
40    if (cache.has(key)) {
41      return cache.get(key);
42    }
43    const result = await fn(...args);
44    cache.set(key, result);
45    return result;
46  };
47}

6. Module de surveillance (monitoring.js)

1// modules/monitoring.js
2import { getDinosaurs } from './api.js';
3import { DinoBehaviorError, EnclosureBreachError } from '../errors/index.js';
4import { formatTimeAgo } from './utils.js';
5
6class ParkMonitor {
7  constructor() {
8    this.intervals = [];
9    this.alertHandlers = [];
10    this.isRunning = false;
11  }
12
13  onAlert(handler) {
14    this.alertHandlers.push(handler);
15    return () => {
16      this.alertHandlers = this.alertHandlers.filter(h => h !== handler);
17    };
18  }
19
20  _fireAlert(alert) {
21    this.alertHandlers.forEach(handler => handler(alert));
22  }
23
24  async checkDinosaurStatuses() {
25    try {
26      const dinosaurs = await getDinosaurs();
27
28      for (const dino of dinosaurs) {
29        const hoursSinceFeeding = (Date.now() - new Date(dino.lastFed).getTime()) / 3600000;
30
31        if (hoursSinceFeeding > 8) {
32          this._fireAlert({
33            type: 'feeding',
34            severity: hoursSinceFeeding > 12 ? 'critical' : 'warning',
35            message: `${dino.name} n'a pas été nourri depuis ${formatTimeAgo(dino.lastFed)}`,
36            dinoId: dino.id
37          });
38        }
39
40        if (dino.behavior === 'escaped') {
41          this._fireAlert({
42            type: 'security',
43            severity: 'critical',
44            message: `BRÈCHE : ${dino.name} (${dino.species}) s'est échappé !`,
45            dinoId: dino.id
46          });
47        }
48      }
49    } catch (error) {
50      console.error('La vérification de surveillance a échoué :', error.message);
51    }
52  }
53
54  start(intervalMs = 30000) {
55    if (this.isRunning) return;
56    this.isRunning = true;
57    console.log('Surveillance du parc démarrée');
58
59    const id = setInterval(() => {
60      this.checkDinosaurStatuses();
61    }, intervalMs);
62
63    this.intervals.push(id);
64    this.checkDinosaurStatuses(); // Exécute immédiatement
65  }
66
67  stop() {
68    this.intervals.forEach(id => clearInterval(id));
69    this.intervals = [];
70    this.isRunning = false;
71    console.log('Surveillance du parc arrêtée');
72  }
73}
74
75export const monitor = new ParkMonitor();

7. Configuration (config.js)

Fichier de configuration de l'application :

1// data/config.js
2
3export const appConfig = {
4  // Paramètres généraux de l'application
5  appName: 'Jurassic Tracker',
6  version: '1.0.0',
7
8  // Paramètres de surveillance
9  monitoring: {
10    dinoBehaviorInterval: 60000, // Intervalle de surveillance du comportement des dinosaures (ms)
11    enclosureSecurityInterval: 90000, // Intervalle de surveillance de la sécurité des enclos (ms)
12    autoStart: true // Démarre automatiquement la surveillance au lancement
13  },
14
15  // Paramètres de l'API
16  api: {
17    baseUrl: 'https://jurassic-park-api.example.com', // En réalité, on utiliserait une vraie URL
18    timeout: 10000, // Délai d'expiration des requêtes API (ms)
19    retryCount: 3 // Nombre de tentatives en cas d'échec des requêtes
20  },
21
22  // Évaluation du risque des dinosaures
23  dinorisk: {
24    dangerousSpecies: ['Tyrannosaurus', 'Velociraptor', 'Spinosaurus', 'Allosaurus', 'Dilophosaurus'],
25    behaviorRiskMatrix: {
26      'calm': { base: 1, factor: 0.8 },
27      'grazing': { base: 1, factor: 0.9 },
28      'sleeping': { base: 2, factor: 0.7 },
29      'exploring': { base: 4, factor: 1.0 },
30      'agitated': { base: 6, factor: 1.2 },
31      'hunting': { base: 7, factor: 1.5 },
32      'aggressive': { base: 9, factor: 2.0 }
33    },
34    hungerRiskHours: 3, // Heures après lesquelles la faim commence à affecter le risque
35    hungerRiskFactor: 0.5 // Facteur d'impact de la faim sur le risque
36  },
37
38  // Niveaux de sécurité pour les notifications
39  security: {
40    alertThresholds: {
41      low: 3,
42      medium: 5,
43      high: 7,
44      critical: 9
45    },
46    autoResolveAlerts: false // Faut-il marquer automatiquement les alertes comme résolues
47  }
48};
49
50export default appConfig;

8. Fichier principal de l'application (index.js)

1// index.js
2import { getDinosaurs } from './modules/api.js';
3import { monitor } from './modules/monitoring.js';
4import { retry } from './modules/utils.js';
5
6async function initialize() {
7  console.log('=== Démarrage de JurassicTracker ===');
8
9  // Configure la gestion des alertes
10  const unsubAlert = monitor.onAlert((alert) => {
11    const icon = alert.severity === 'critical' ? '!!' : '!';
12    console.log(`${icon} [${alert.type.toUpperCase()}] ${alert.message}`);
13  });
14
15  try {
16    // Récupère les données initiales avec une logique de réessai
17    const dinosaurs = await retry(() => getDinosaurs(), 3, 1000);
18    console.log(`${dinosaurs.length} dinosaures chargés`);
19
20    // Démarre la surveillance en arrière-plan
21    monitor.start(10000); // Vérifie toutes les 10 secondes
22
23    console.log('=== JurassicTracker prêt ===');
24
25    // Nettoyage à l'arrêt
26    const cleanup = () => {
27      monitor.stop();
28      unsubAlert();
29      console.log('JurassicTracker arrêté proprement');
30    };
31
32    process.on('SIGINT', cleanup);
33    process.on('SIGTERM', cleanup);
34
35  } catch (error) {
36    console.error("Erreur fatale d'initialisation :", error.message);
37    process.exit(1);
38  }
39}
40
41initialize();

Récapitulatif des techniques appliquées

Dans notre application JurassicTracker, nous avons utilisé toutes les techniques les plus importantes apprises dans ce module :

  1. Modules ES - toute l'app est organisée en modules qui importent et exportent des fonctionnalités
  2. Asynchronie :
    • On utilise async/await pour une gestion propre des opérations asynchrones
    • On utilise Promise.all pour exécuter des opérations en parallèle
    • On gère les délais et les intervalles de temps
  3. Gestion des erreurs :
    • On a créé une hiérarchie de classes d'erreurs personnalisées (APIError, DinoError, SecurityError)
    • On utilise des blocs try/catch pour la gestion locale des erreurs
    • On implémente une logique de réessai (retry) pour les opérations qui peuvent être relancées
  4. Surveillance en arrière-plan :
    • On utilise setInterval pour les tâches de surveillance cycliques
    • On implémente un système de notifications basé sur le pattern Observer
  5. Gestion de l'état de l'application :
    • On stocke l'état dans des variables de module
    • On utilise Map pour un stockage efficace des données
    • On implémente la mémoïsation pour l'optimisation

Cette application montre comment combiner toutes les techniques apprises en un système fonctionnel, modulaire et tolérant aux pannes.

Dans l'exercice suivant, nous approfondirons la structuration du code dans des projets JavaScript encore plus grands, en étendant les techniques et patterns que nous avons appris.

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. Quelle est la bonne pratique pour organiser les modules d'un projet JavaScript ?

Tâches pratiques dans le jeu

  • Éditeur de code

    Crée une DinosaurDatabase avec ses méthodes et un ParkMonitor avec gestion des erreurs.

  • Tri vertical

    Ordonne les couches du projet, de la plus basse à la plus haute :

Articles utiles