Cours JavaScript et TypeScript · Module 5: JavaScript avancé
Implémenter une application avec les techniques apprises
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 :
- Du code modulaire (modules ES)
- Des opérations asynchrones (Promises, async/await)
- La gestion des erreurs (try/catch, classes d'erreurs personnalisées)
- Des services en arrière-plan (setInterval)
- 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'applicationImplé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 :
- Modules ES - toute l'app est organisée en modules qui importent et exportent des fonctionnalités
- 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
- 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
- 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
- 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. 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 :