Cours JavaScript et TypeScript · Module 4: JavaScript asynchrone

Projet final : système de surveillance asynchrone du Parc Jurassique

10 min de lecture
Dans cette leçon4

Félicitations - tu as atteint le projet final de ce module ! Tout au long du module, tu as étudié l'asynchronicité en JavaScript - des callbacks aux Promises, jusqu'à l'élégant async/await. Il est temps maintenant de combiner toutes ces compétences en un système cohérent.

Imagine que la direction du Parc Jurassique t'a chargé de construire un système de surveillance central qui collecte les données de tous les secteurs du parc en temps réel, analyse les menaces et coordonne les réponses aux incidents. Le système doit gérer de nombreuses opérations simultanément - des lectures des capteurs de mouvement dans les enclos des raptors, à la surveillance des systèmes d'alimentation, en passant par la communication avec les équipes de terrain. Chacune de ces opérations prend un temps différent et peut échouer - et dans un parc rempli de dinosaures, il n'y a pas de place pour des erreurs non gérées !

Dans ce projet, tu vas construire un système de surveillance complet en utilisant les callbacks, les Promises, async/await, la gestion des erreurs et l'exécution parallèle d'opérations asynchrones.

Objectif du projet

Tu vas créer un système de surveillance asynchrone du Parc Jurassique composé de quatre sous-systèmes :

  1. Module Capteurs - simulation des lectures de capteurs (callbacks)
  2. Module Communication - échange de données entre secteurs (Promises)
  3. Module Analyse des menaces - traitement des données et classification des alertes (async/await)
  4. Coordinateur central - gestion parallèle de tous les sous-systèmes (Promise.all/race/allSettled)

Exigences fonctionnelles

1. Module Capteurs (Callbacks)

Le premier sous-système est la couche de bas niveau qui simule les lectures des capteurs répartis dans tout le parc. Nous utilisons les callbacks car ils représentent le pattern asynchrone le plus ancien - semblable aux anciens systèmes du parc tournant sur une technologie héritée.

1// Simulation de lecture de capteur - en utilisant des callbacks
2function readSensor(sensorId, sectorName, callback) {
3  const delay = Math.random() * 2000 + 500; // 0.5-2.5 secondes
4
5  setTimeout(() => {
6    // 15% de chance de panne du capteur
7    if (Math.random() < 0.15) {
8      callback(new Error(`Le capteur ${sensorId} dans le secteur ${sectorName} ne répond pas`), null);
9      return;
10    }
11
12    const sensorData = {
13      sensorId,
14      sector: sectorName,
15      timestamp: new Date().toISOString(),
16      temperature: Math.round(20 + Math.random() * 25),
17      motion: Math.random() > 0.5,
18      fenceIntegrity: Math.round(70 + Math.random() * 30),
19      powerLevel: Math.round(50 + Math.random() * 50)
20    };
21
22    callback(null, sensorData);
23  }, delay);
24}
25
26// Lecture de plusieurs capteurs d'un même secteur - pattern callback
27function scanSector(sectorName, sensorCount, callback) {
28  const results = [];
29  const errors = [];
30  let completed = 0;
31
32  for (let i = 1; i <= sensorCount; i++) {
33    readSensor(`SENSOR-${sectorName}-${i}`, sectorName, (error, data) => {
34      completed++;
35
36      if (error) {
37        errors.push(error.message);
38      } else {
39        results.push(data);
40      }
41
42      // Quand tous les capteurs ont répondu
43      if (completed === sensorCount) {
44        callback({
45          sector: sectorName,
46          successfulReads: results,
47          failedReads: errors,
48          scanCompletedAt: new Date().toISOString()
49        });
50      }
51    });
52  }
53}

Remarque le pattern du « compteur de callbacks » - on compte les opérations terminées et on n'appelle le callback final que lorsque toutes ont fini.

2. Module Communication (Promises)

Le deuxième sous-système gère la communication entre les secteurs du parc. Au lieu de callbacks, nous utilisons des Promises, ce qui permet un chaînage des opérations plus lisible et une meilleure gestion des erreurs.

1// Convertir un callback en Promise (promisification)
2function readSensorAsync(sensorId, sectorName) {
3  return new Promise((resolve, reject) => {
4    readSensor(sensorId, sectorName, (error, data) => {
5      if (error) reject(error);
6      else resolve(data);
7    });
8  });
9}
10
11// Envoyer une alerte au centre de commande
12function sendAlert(alertData) {
13  return new Promise((resolve, reject) => {
14    const delay = Math.random() * 1000 + 200;
15
16    setTimeout(() => {
17      if (Math.random() < 0.1) {
18        reject(new Error(`Échec de l'envoi de l'alerte : système de communication surchargé`));
19        return;
20      }
21
22      resolve({
23        alertId: `ALERT-${Date.now()}`,
24        status: "sent",
25        data: alertData,
26        confirmedAt: new Date().toISOString()
27      });
28    }, delay);
29  });
30}
31
32// Chaîne de Promises - lire, analyser, alerter
33function monitorAndAlert(sensorId, sectorName) {
34  return readSensorAsync(sensorId, sectorName)
35    .then(sensorData => {
36      console.log(`Lecture de ${sensorId} : temp=${sensorData.temperature}°C, clôture=${sensorData.fenceIntegrity}%`);
37
38      // Analyse des données - y a-t-il une menace ?
39      if (sensorData.fenceIntegrity < 80 || sensorData.motion) {
40        return {
41          level: sensorData.fenceIntegrity < 75 ? "CRITICAL" : "WARNING",
42          sensor: sensorId,
43          sector: sectorName,
44          details: sensorData
45        };
46      }
47      return null; // Aucune menace
48    })
49    .then(threat => {
50      if (threat) {
51        return sendAlert(threat);
52      }
53      return { status: "no threats", sensor: sensorId };
54    })
55    .catch(error => {
56      console.error(`Erreur de surveillance ${sensorId} : ${error.message}`);
57      return { status: "error", sensor: sensorId, error: error.message };
58    })
59    .finally(() => {
60      console.log(`Cycle de surveillance terminé pour ${sensorId}`);
61    });
62}

Remarque comment .then(), .catch() et .finally() créent un flux de données lisible avec une gestion complète des erreurs.

3. Module Analyse des menaces (async/await)

Le troisième sous-système est la couche analytique qui traite les données des capteurs et classifie les menaces. Nous utilisons async/await pour une lisibilité maximale du code.

1// Classification du niveau de menace
2async function classifyThreat(sectorData) {
3  // Simuler le temps de traitement par IA
4  await new Promise(resolve => setTimeout(resolve, 300));
5
6  const { successfulReads, failedReads } = sectorData;
7
8  const avgFenceIntegrity = successfulReads.reduce(
9    (sum, s) => sum + s.fenceIntegrity, 0
10  ) / successfulReads.length;
11
12  const motionDetected = successfulReads.filter(s => s.motion).length;
13  const sensorFailures = failedReads.length;
14
15  let threatLevel = "GREEN"; // Sûr
16  const warnings = [];
17
18  if (avgFenceIntegrity < 75) {
19    threatLevel = "RED";
20    warnings.push(`État critique de la clôture : ${avgFenceIntegrity.toFixed(1)}%`);
21  } else if (avgFenceIntegrity < 85) {
22    threatLevel = "YELLOW";
23    warnings.push(`Intégrité de la clôture dégradée : ${avgFenceIntegrity.toFixed(1)}%`);
24  }
25
26  if (motionDetected > successfulReads.length * 0.6) {
27    threatLevel = "RED";
28    warnings.push(`Mouvement intense détecté sur ${motionDetected} capteurs`);
29  }
30
31  if (sensorFailures > 2) {
32    warnings.push(`${sensorFailures} capteurs ne répondent pas - sabotage possible !`);
33    if (threatLevel !== "RED") threatLevel = "YELLOW";
34  }
35
36  return {
37    sector: sectorData.sector,
38    threatLevel,
39    warnings,
40    avgFenceIntegrity: avgFenceIntegrity.toFixed(1),
41    activeSensors: successfulReads.length,
42    failedSensors: sensorFailures,
43    motionDetections: motionDetected,
44    analyzedAt: new Date().toISOString()
45  };
46}
47
48// Fonction principale d'analyse de secteur avec gestion des erreurs
49async function analyzeSector(sectorName, sensorCount) {
50  console.log(`\nDémarrage de l'analyse du secteur : ${sectorName}`);
51
52  try {
53    // Scanner le secteur (convertir le callback en Promise)
54    const scanResult = await new Promise((resolve) => {
55      scanSector(sectorName, sensorCount, resolve);
56    });
57
58    console.log(`Scan terminé : ${scanResult.successfulReads.length} lectures, ${scanResult.failedReads.length} erreurs`);
59
60    // Classifier la menace
61    const analysis = await classifyThreat(scanResult);
62
63    // Si une menace est détectée, envoyer une alerte
64    if (analysis.threatLevel !== "GREEN") {
65      try {
66        const alertResult = await sendAlert({
67          type: "SECTOR_ANALYSIS",
68          ...analysis
69        });
70        console.log(`Alerte envoyée : ${alertResult.alertId}`);
71        analysis.alertSent = true;
72        analysis.alertId = alertResult.alertId;
73      } catch (alertError) {
74        console.error(`Échec de l'envoi de l'alerte : ${alertError.message}`);
75        analysis.alertSent = false;
76        analysis.alertError = alertError.message;
77      }
78    }
79
80    return analysis;
81
82  } catch (error) {
83    console.error(`Erreur critique d'analyse pour le secteur ${sectorName} : ${error.message}`);
84    return {
85      sector: sectorName,
86      threatLevel: "UNKNOWN",
87      error: error.message,
88      analyzedAt: new Date().toISOString()
89    };
90  }
91}

Remarque les try/catch imbriqués - le bloc externe gère les erreurs critiques d'analyse, tandis que le bloc interne permet à l'analyse de continuer même si l'envoi d'une alerte échoue.

4. Coordinateur central (Promise.all / race / allSettled)

Le dernier sous-système est le cœur du système de surveillance. Il coordonne le scan parallèle de tous les secteurs du parc et prend des décisions basées sur les données agrégées.

1// Configuration des secteurs du parc
2const parkSectors = [
3  { name: "T-Rex-Paddock", sensors: 5 },
4  { name: "Raptor-Pen", sensors: 4 },
5  { name: "Herbivore-Valley", sensors: 6 },
6  { name: "Aviary", sensors: 3 },
7  { name: "Marine-Exhibit", sensors: 4 }
8];
9
10// Scan complet du parc - tous les secteurs en parallèle
11async function fullParkScan() {
12  console.log("=== DÉMARRAGE DU SCAN COMPLET DU PARC ===\n");
13  const startTime = Date.now();
14
15  // Promise.allSettled - continuer même si un secteur échoue
16  const sectorPromises = parkSectors.map(
17    sector => analyzeSector(sector.name, sector.sensors)
18  );
19
20  const results = await Promise.allSettled(sectorPromises);
21
22  const report = {
23    scanDuration: `${((Date.now() - startTime) / 1000).toFixed(1)}s`,
24    sectors: [],
25    overallStatus: "GREEN"
26  };
27
28  results.forEach((result, index) => {
29    if (result.status === "fulfilled") {
30      report.sectors.push(result.value);
31      if (result.value.threatLevel === "RED") {
32        report.overallStatus = "RED";
33      } else if (result.value.threatLevel === "YELLOW" && report.overallStatus !== "RED") {
34        report.overallStatus = "YELLOW";
35      }
36    } else {
37      report.sectors.push({
38        sector: parkSectors[index].name,
39        threatLevel: "ERROR",
40        error: result.reason?.message || "Erreur inconnue"
41      });
42      report.overallStatus = "RED";
43    }
44  });
45
46  return report;
47}
48
49// Scan chronométré - Promise.race
50async function timedScan(sectorName, sensorCount, timeoutMs) {
51  const scanPromise = analyzeSector(sectorName, sensorCount);
52
53  const timeoutPromise = new Promise((_, reject) => {
54    setTimeout(() => {
55      reject(new Error(`Timeout : le scan du secteur ${sectorName} a dépassé ${timeoutMs}ms`));
56    }, timeoutMs);
57  });
58
59  try {
60    return await Promise.race([scanPromise, timeoutPromise]);
61  } catch (error) {
62    return {
63      sector: sectorName,
64      threatLevel: "TIMEOUT",
65      error: error.message,
66      analyzedAt: new Date().toISOString()
67    };
68  }
69}
70
71// Lancer le scan complet et afficher le rapport
72async function runParkMonitoring() {
73  try {
74    const report = await fullParkScan();
75
76    console.log("\n=== RAPPORT FINAL ===");
77    console.log(`Durée du scan : ${report.scanDuration}`);
78    console.log(`Statut global : ${report.overallStatus}`);
79    console.log("\nSecteurs :");
80
81    report.sectors.forEach(sector => {
82      const icon = sector.threatLevel === "GREEN" ? "OK" :
83                   sector.threatLevel === "YELLOW" ? "!" :
84                   sector.threatLevel === "RED" ? "!!" : "?";
85      console.log(`  ${icon} ${sector.sector} : ${sector.threatLevel}`);
86
87      if (sector.warnings?.length > 0) {
88        sector.warnings.forEach(w => console.log(`     → ${w}`));
89      }
90    });
91
92    if (report.overallStatus === "RED") {
93      console.log("\nALERTE : menaces critiques détectées ! Activation du protocole d'urgence.");
94    }
95
96    return report;
97  } catch (error) {
98    console.error("Erreur critique du système de surveillance :", error.message);
99  }
100}
101
102// Démarrer le système de surveillance
103runParkMonitoring();

Recommandations d'implémentation

Lorsque tu construis ton propre système de surveillance, garde à l'esprit quelques principes clés :

Structure des fichiers :

1// sensors.js      - module capteurs (callbacks)
2// communication.js - module communication (Promises)
3// analysis.js     - module analyse (async/await)
4// coordinator.js  - coordinateur central (Promise.all/race/allSettled)
5// main.js         - point d'entrée

Gestion des erreurs à chaque niveau :

1// Niveau 1 : Callback - pattern error-first
2function callbackOperation(callback) {
3  // callback(error, data) - toujours vérifier error
4}
5
6// Niveau 2 : Promise - .catch() à la fin de la chaîne
7promiseOperation()
8  .then(result => process(result))
9  .catch(error => handleError(error));
10
11// Niveau 3 : async/await - try/catch avec finally
12async function asyncOperation() {
13  try {
14    const result = await riskyOperation();
15    return result;
16  } catch (error) {
17    logError(error);
18    return fallbackValue;
19  } finally {
20    cleanup();
21  }
22}

Choisir la bonne méthode Promise selon les scénarios :

1// Toutes doivent réussir → Promise.all()
2const allResults = await Promise.all([task1(), task2()]);
3
4// Continuer malgré les erreurs → Promise.allSettled()
5const mixedResults = await Promise.allSettled([task1(), task2()]);
6
7// La plus rapide gagne → Promise.race()
8const fastest = await Promise.race([task1(), timeout(5000)]);
9
10// Premier succès → Promise.any()
11const firstSuccess = await Promise.any([server1(), server2()]);

Bonne chance !

Ce projet est ta chance de montrer que tu maîtrises l'asynchronicité en JavaScript. Comme dans un vrai Parc Jurassique, ton système doit être résistant aux pannes, rapide à réagir et capable de gérer de nombreuses opérations simultanément. Souviens-toi des mots du Dr Ian Malcolm : « La vie trouve toujours un chemin » - et ton code doit y être préparé !

Construis ton système de surveillance dans l'éditeur ci-dessous. Implémente au moins trois des quatre sous-systèmes et assure-toi que le système gère correctement les erreurs et affiche un rapport final lisible.

Code de cette leçon : index.js
1// Projet final: Système de monitoring asynchrone
2// Combine: callbacks, Promise, async/await, gestion des erreurs
3
4// === 1. MODULE CAPTEURS (Callbacks) ===
5// TODO: Crée la fonction readSensor(sensorId, sectorName, callback)
6// Simule une lecture de capteur avec un délai (setTimeout)
7function readSensor(sensorId, sectorName, callback) {
8  setTimeout(() => {
9    // TODO: 15% de chance d'erreur du capteur
10    // TODO: Retourne les données: sensorId, sector, temperature, motion, fenceIntegrity
11    const data = {
12      sensorId,
13      sector: sectorName,
14      timestamp: new Date().toISOString(),
15      // TODO: Add des valeurs aléatoires de capteurs
16    };
17    callback(null, data);
18  }, Math.random() * 1000 + 500);
19}
20
21// === 2. MODULE COMMUNICATION (Promise) ===
22// TODO: Crée la fonction sendAlert(sector, message) qui retourne une Promise
23function sendAlert(sector, message) {
24  // TODO: Retourne new Promise qui se résout après un délai
25  // Simule 10% de chance d'erreur de communication
26}
27
28// === 3. MODULE ANALYSE DES MENACES (async/await) ===
29// TODO: Crée la fonction async analyzeThreat(sectorData)
30async function analyzeThreat(sectorData) {
31  // TODO: Utilise await to retrieve data
32  // TODO: Utilise try/catch pour la gestion des erreurs
33  // TODO: Classifie la menace : "critical", "warning", "normal"
34}
35
36// === 4. COORDINATEUR CENTRAL ===
37// TODO: Crée la fonction async runParkMonitoring()
38// Utilise Promise.all pour scanner plusieurs secteurs en parallèle
39async function runParkMonitoring() {
40  console.log("=== DÉMARRAGE DU MONITORING ===");
41  const sectors = ["A-1", "B-2", "C-3"];
42
43  // TODO: Utilise Promise.allSettled pour scanner tous les secteurs
44  // TODO: Affiche un rapport avec les résultats
45
46  console.log("=== MONITORING TERMINÉ ===");
47}
48
49// Démarre le système :
50runParkMonitoring();

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

Articles utiles