Cours JavaScript et TypeScript · Module 4: JavaScript asynchrone

Event Loop (modèle d'événements)

17 min de lecture
Dans cette leçon8

Bienvenue de retour au Parc Jurassique ! Jusqu'à présent, nous nous sommes concentrés sur les bases de la programmation orientée objet - construction de classes, héritage et composition. Cependant, dans des applications réelles comme un système de gestion de parc à dinosaures, nous devons souvent gérer des opérations qui prennent beaucoup de temps - de la récupération de données depuis les capteurs des enclos, à la communication avec les systèmes de sécurité, en passant par la gestion des interactions avec les visiteurs du parc.

Dans ce module, nous allons aborder un aspect clé de JavaScript : sa nature asynchrone et le modèle d'événements sur lequel il repose. Comprendre ce mécanisme est essentiel pour construire des applications réactives et efficaces, capables de gérer de nombreuses opérations simultanément - ce qui est crucial pour la sécurité d'un parc rempli de prédateurs !

JavaScript - un langage mono-thread aux capacités asynchrones

JavaScript est un langage mono-thread (single-threaded). Cela signifie qu'il ne peut exécuter qu'une seule opération à la fois. C'est un peu comme n'avoir qu'un seul employé pour gérer toutes les tâches du parc.

Imagine que cet employé (le thread JavaScript) doive :

  • Vérifier l'état des clôtures
  • Surveiller les signes vitaux des dinosaures
  • Répondre aux messages des visiteurs
  • Contrôler les systèmes d'urgence

S'il effectuait ces tâches de façon synchrone (l'une après l'autre), il devrait terminer complètement une tâche avant de passer à la suivante. Ce serait inefficace et potentiellement dangereux !

Heureusement, JavaScript dispose d'un système ingénieux appelé « event loop » (boucle d'événements) qui lui permet de gérer des opérations asynchrones malgré son fonctionnement mono-thread.

Event Loop - le cœur du JavaScript asynchrone

Pour comprendre l'event loop, imaginons le centre de contrôle du Parc Jurassique. Dans ce centre, nous avons :

  1. Call Stack (pile d'appels) - une liste des tâches en cours d'exécution (les tâches principales de notre employé)
  2. Callback Queue (file de callbacks) - une liste des tâches en attente d'exécution une fois les opérations asynchrones terminées
  3. Web APIs (dans les navigateurs) ou C++ APIs (dans Node.js) - des systèmes externes capables d'effectuer des tâches « en arrière-plan »
  4. Event Loop - un mécanisme qui vérifie en continu si la pile d'appels est vide, et si c'est le cas, déplace les tâches de la file vers la pile

Voyons comment cela fonctionne avec un exemple du Parc Jurassique :

1console.log("1. Démarrage de la ronde matinale du parc.");
2
3setTimeout(() => {
4  console.log("4. Données reçues des capteurs de l'enclos des dinosaures.");
5}, 2000);
6
7fetch('https://jurassic-park-api.com/security-status')
8  .then(response => response.json())
9  .then(data => {
10    console.log("5. État du système de sécurité :", data.status);
11  });
12
13console.log("2. Vérification du planning du personnel d'aujourd'hui.");
14console.log("3. Ronde matinale terminée.");

Si nous exécutons ce code, nous verrons dans la console :

11. Démarrage de la ronde matinale du parc.
22. Vérification du planning du personnel d'aujourd'hui.
33. Ronde matinale terminée.
44. Données reçues des capteurs de l'enclos des dinosaures.
55. État du système de sécurité : active

Que s'est-il passé ? Analysons cela étape par étape :

  1. console.log("1. Démarrage de la ronde matinale du parc.") est ajouté à la pile d'appels, exécuté, puis retiré de la pile.

  2. setTimeout(...) est ajouté à la pile d'appels. JavaScript reconnaît qu'il s'agit d'un appel asynchrone et le transmet à la Web API, qui démarre le minuteur. setTimeout est retiré de la pile.

  3. fetch(...) est ajouté à la pile d'appels. JavaScript reconnaît qu'il s'agit d'un appel asynchrone et le transmet à la Web API, qui envoie la requête HTTP. fetch est retiré de la pile.

  4. console.log("2. Vérification du planning du personnel d'aujourd'hui.") est ajouté à la pile, exécuté, puis retiré.

  5. console.log("3. Ronde matinale terminée.") est ajouté à la pile, exécuté, puis retiré.

  6. La pile d'appels est maintenant vide, mais deux opérations asynchrones sont en cours :

    • Le minuteur de setTimeout
    • La requête HTTP de fetch
  7. Au bout de 2 secondes, le minuteur expire et le callback de setTimeout est ajouté à la file de tâches.

  8. L'event loop voit que la pile d'appels est vide, il déplace donc le callback de la file vers la pile.

  9. console.log("4. Données reçues des capteurs de l'enclos des dinosaures.") est exécuté puis retiré de la pile.

  10. Pendant ce temps, la requête HTTP se termine, les données sont récupérées et le callback .then() est ajouté à la file.

  11. L'event loop déplace à nouveau le callback de la file vers la pile.

  12. Les données sont traitées et console.log("5. État du système de sécurité :", data.status) est exécuté.

Event Loop - analyse détaillée

Pour mieux comprendre l'event loop, dessinons ses principaux composants :

1┌─────────────┐    ┌───────────┐    ┌───────────────────┐
2│             │    │           │    │                   │
3│ Call Stack  │    │ Web APIs  │    │  Callback Queue   │
4│             │    │           │    │                   │
5└─────────────┘    └───────────┘    └───────────────────┘
6       ↑                 │                  │
7       │                 ↓                  │
8       │            ┌────────┐              │
9       └────────────│  Event │←─────────────┘
10                    │  Loop  │
11                    └────────┘

Dans la représentation graphique :

  1. Call Stack - une pile LIFO (Last In, First Out) qui stocke les appels de fonctions en cours d'exécution
  2. Web APIs - interfaces du navigateur/environnement pour les opérations asynchrones (setTimeout, fetch, Event Listeners)
  3. Callback Queue - une file FIFO (First In, First Out) qui stocke les callbacks prêts à être exécutés
  4. Event Loop - vérifie en continu si la pile d'appels est vide, et si c'est le cas, déplace les fonctions de la file vers la pile

Le plus étonnant, c'est que tout cela tourne sur un seul thread ! JavaScript n'exécute pas ces tâches en parallèle, mais bascule entre elles de manière à donner l'impression que tout se passe simultanément.

Microtasks et Macrotasks

Dans les implémentations JavaScript récentes (et dans la spécification), on distingue deux types de files :

  1. Microtask Queue - pour les tâches de plus haute priorité, comme les callbacks de Promise
  2. Macrotask Queue / Task Queue - pour les tâches de plus basse priorité, comme setTimeout, setInterval, I/O

Lorsque la pile est vide, toutes les tâches de la file de microtasks sont exécutées en premier, puis celles de la file de macrotasks.

1console.log("1. Début du nourrissage matinal des dinosaures.");
2
3// Macrotask (setTimeout)
4setTimeout(() => {
5  console.log("4. Vérification de l'état de santé des dinosaures.");
6}, 0);
7
8// Microtask (Promise)
9Promise.resolve().then(() => {
10  console.log("3. Lecture des données du système de sécurité.");
11});
12
13console.log("2. Préparation de la nourriture pour les prédateurs.");

Résultat :

11. Début du nourrissage matinal des dinosaures.
22. Préparation de la nourriture pour les prédateurs.
33. Lecture des données du système de sécurité.
44. Vérification de l'état de santé des dinosaures.

Remarque que, bien que les deux tâches asynchrones (setTimeout avec 0ms et Promise.resolve()) soient prêtes à s'exécuter presque immédiatement, la Promise (microtask) s'exécute avant le setTimeout (macrotask).

Bloquer l'Event Loop

Comme JavaScript est mono-thread, les opérations longues peuvent « bloquer » l'event loop et empêcher les autres tâches de s'exécuter. C'est comme si l'employé du centre de contrôle restait bloqué devant un seul écran sans pouvoir vérifier les autres systèmes.

1console.log("Démarrage de la simulation de croissance des dinosaures...");
2
3// Fonction qui bloque l'exécution pendant 5 secondes
4function heavyComputation() {
5  const startTime = Date.now();
6  while (Date.now() - startTime < 5000) {
7    // Calculs intensifs...
8  }
9}
10
11heavyComputation();
12console.log("Simulation terminée !"); // Après 5 secondes
13
14// Ce code ne s'exécutera pas avant 5 secondes, même s'il est asynchrone !
15setTimeout(() => {
16  console.log("Vérification des systèmes de sécurité...");
17}, 1000);

Dans cet exemple, la fonction heavyComputation() bloque le thread JavaScript pendant 5 secondes. Pendant ce temps, aucune autre tâche ne peut être exécutée, y compris les tâches asynchrones. C'est comme si l'employé du parc restait bloqué sur une tâche sans pouvoir répondre aux alarmes ou à d'autres signaux importants.

Dans un vrai Parc Jurassique, un tel blocage pourrait être catastrophique - un raptor pourrait s'échapper et le système ne l'enregistrerait pas car il est occupé à autre chose !

C'est pourquoi, en JavaScript, on essaie de :

  1. Éviter les opérations synchrones longues
  2. Découper les calculs complexes en morceaux plus petits
  3. Utiliser les Web Workers pour les opérations gourmandes en CPU

Ordre d'exécution des tâches - exemple pratique

Analysons un exemple plus complexe qui illustre l'ordre d'exécution de différents types de tâches au Parc Jurassique :

1// Simulation du système de contrôle du Parc Jurassique
2console.log("1. Démarrage du système de contrôle du Parc Jurassique...");
3
4// Macrotask (setTimeout)
5setTimeout(() => {
6  console.log("6. Vérification des clôtures électriques...");
7
8  // Microtask imbriquée dans une macrotask
9  Promise.resolve().then(() => {
10    console.log("7. Vérification du statut des prédateurs...");
11  });
12}, 0);
13
14// Microtask (Promise)
15Promise.resolve().then(() => {
16  console.log("3. Initialisation des caméras de sécurité...");
17
18  // Microtask imbriquée
19  Promise.resolve().then(() => {
20    console.log("4. Calibration des capteurs de mouvement...");
21  });
22});
23
24// Une autre microtask
25Promise.resolve().then(() => {
26  console.log("5. Lancement des protocoles d'urgence...");
27});
28
29// Code synchrone
30console.log("2. Vérification de l'état du système...");

Résultat :

11. Démarrage du système de contrôle du Parc Jurassique...
22. Vérification de l'état du système...
33. Initialisation des caméras de sécurité...
44. Calibration des capteurs de mouvement...
55. Lancement des protocoles d'urgence...
66. Vérification des clôtures électriques...
77. Vérification du statut des prédateurs...

Cet exemple montre que :

  1. Le code synchrone s'exécute en premier (1, 2)
  2. Puis toutes les tâches de la file de microtasks s'exécutent (3, 4, 5)
  3. Ce n'est qu'ensuite que les tâches de la file de macrotasks s'exécutent (6)
  4. Les microtasks créées pendant une macrotask (7) s'exécutent immédiatement après la fin de la macrotask en cours

Bonus : Var, Let, Const - quand utiliser lequel ?

Lorsqu'on travaille avec du code asynchrone, il est important de déclarer correctement les variables. La manière de déclarer affecte leur comportement dans les blocs et les fonctions, ce qui a un impact direct sur le code asynchrone (par exemple, les variables dans des boucles avec setTimeout). Tout comme le parc dispose de différents types de clôtures selon les espèces de dinosaures (électrique pour le T-Rex, standard pour le Triceratops), JavaScript propose différentes façons de déclarer les variables : var, let et const.

Const - toujours le choix par défaut

const signifie une constante - une valeur qui ne peut pas être réassignée. Au Parc Jurassique, on utilise const pour les éléments qui ne doivent pas changer, comme les paramètres de sécurité ou la configuration du système :

1// Configuration du système - ne devrait pas changer
2const MAX_DINOSAURS_PER_ENCLOSURE = 5;
3const FENCE_MINIMUM_VOLTAGE = 10000; // volts
4const EMERGENCY_CONTACT = "+1-555-JURASSIC";
5
6// Tenter de modifier une const provoque une erreur
7MAX_DINOSAURS_PER_ENCLOSURE = 10; // TypeError: Assignment to constant variable

Quand utiliser const :

  • Pour les valeurs qui ne changeront pas
  • Pour les objets et les tableaux (même si leur contenu change)
  • Comme choix par défaut - en cas de doute, utilise const

Important : const ne signifie pas que le contenu d'un objet ou d'un tableau est immuable !

1// C'est correct - on modifie le contenu, pas l'assignation
2const dinosaurs = [];
3dinosaurs.push({ name: "Rexy", species: "T-Rex" }); // OK !
4dinosaurs.push({ name: "Blue", species: "Velociraptor" }); // OK !
5
6const parkConfig = {
7  openTime: "09:00",
8  closeTime: "18:00"
9};
10parkConfig.closeTime = "20:00"; // OK - on modifie une propriété
11
12// Ceci provoque une erreur - tentative de réassignation
13dinosaurs = []; // TypeError !
14parkConfig = {}; // TypeError !

Let - quand la valeur change

let s'utilise lorsque la valeur d'une variable change pendant l'exécution du programme. Au Parc Jurassique, on utilise let pour les éléments dynamiques comme les compteurs, les accumulateurs ou les variables de boucle :

1// Compteur de visiteurs - change tout au long de la journée
2let visitorCount = 0;
3
4function addVisitors(count) {
5  visitorCount += count;
6  console.log(`Nombre actuel de visiteurs : ${visitorCount}`);
7}
8
9addVisitors(50); // 50
10addVisitors(30); // 80
11addVisitors(20); // 100

Quand utiliser let :

  • Dans les boucles (for, while)
  • Pour les compteurs et les accumulateurs
  • Quand la valeur sera réassignée
1// Boucles - cas d'usage classique de let
2for (let i = 0; i < 5; i++) {
3  console.log(`Vérification du dinosaure n°${i}`);
4}
5
6// Accumulateurs
7let totalFeedingCost = 0;
8
9const dinosaurs = [
10  { name: "Rexy", dailyFoodCost: 500 },
11  { name: "Blue", dailyFoodCost: 150 },
12  { name: "Trixie", dailyFoodCost: 200 }
13];
14
15for (const dino of dinosaurs) {
16  totalFeedingCost += dino.dailyFoodCost;
17}
18
19console.log(`Coût total du nourrissage : ${totalFeedingCost} $`);

Var - presque jamais (code hérité)

var est l'ancienne façon de déclarer des variables en JavaScript. Son comportement est problématique et ne devrait pas être utilisé dans du code moderne. Le seul endroit où tu verras var, c'est dans de l'ancien code (legacy) écrit avant 2015 (avant ES6).

Problèmes avec var :

  1. Portée de fonction au lieu de portée de bloc - var « s'échappe » des blocs
1// Problème : var ignore les blocs
2function checkDinosaur(isDangerous) {
3  if (isDangerous) {
4    var alert = "DANGER !"; // var est accessible en dehors du bloc if !
5  }
6
7  console.log(alert); // "DANGER !" - fuit hors du bloc if !
8  // Avec let/const, on obtiendrait une ReferenceError
9}
10
11checkDinosaur(true);
12
13// Comparons avec let :
14function checkDinosaurProper(isDangerous) {
15  if (isDangerous) {
16    let alert = "DANGER !";
17  }
18
19  console.log(alert); // ReferenceError: alert is not defined
20}
  1. Hoisting avec undefined - var est « hissé » (hoisted) en haut de la fonction
1// Comportement étrange de var
2function feedDinosaurs() {
3  console.log(food); // undefined (et non ReferenceError !)
4  var food = "viande";
5  console.log(food); // "viande"
6}
7
8// C'est interprété comme :
9function feedDinosaurs() {
10  var food; // hoisting - déclaration en haut
11  console.log(food); // undefined
12  food = "viande"; // assignation
13  console.log(food); // "viande"
14}
  1. Peut être redéclarée - on peut écraser des variables par accident
1var dinosaurName = "Rexy";
2// On réutilise accidentellement le même nom
3var dinosaurName = "Blue"; // Aucune erreur ! La valeur précédente est écrasée
4
5console.log(dinosaurName); // "Blue" - Rexy a disparu !
6
7// Avec let/const :
8let dinoName = "Rexy";
9let dinoName = "Blue"; // SyntaxError: Identifier 'dinoName' has already been declared

Quand utiliser var :

  • Jamais dans du nouveau code
  • Uniquement en éditant du très vieux code dont tu dois préserver la compatibilité

Exemple pratique - revue de code

Imagine que tu reçois le code d'un collègue à relire. Voici comment améliorer l'usage des variables :

Mauvais code (avant) :

1// Mauvais - tout en var
2var parkName = "Jurassic Park"; // Ne change pas - devrait être const
3var isOpen = true; // Change - devrait être let
4var maxCapacity = 10000; // Ne change pas - devrait être const
5
6function processVisitors() {
7  var totalVisitors = 0; // Accumulateur - devrait être let
8
9  for (var i = 0; i < 100; i++) { // Itérateur - devrait être let
10    totalVisitors++;
11  }
12
13  console.log(i); // 100 - var fuit ! Problème !
14  return totalVisitors;
15}

Bon code (après) :

1// Bon - const par défaut
2const parkName = "Jurassic Park";
3const maxCapacity = 10000;
4
5// let uniquement quand la valeur change
6let isOpen = true;
7
8function processVisitors() {
9  // let pour l'accumulateur
10  let totalVisitors = 0;
11
12  // let dans la boucle - portée de bloc
13  for (let i = 0; i < 100; i++) {
14    totalVisitors++;
15  }
16
17  // console.log(i); // ReferenceError - i est limité à la boucle
18  return totalVisitors;
19}
20
21// On peut modifier isOpen
22isOpen = false;
23isOpen = true;
24
25// Mais on ne peut pas modifier une const
26// parkName = "Dino World"; // TypeError !

Autre exemple - système de gestion des dinosaures

1class DinosaurManagementSystem {
2  constructor() {
3    // const pour les propriétés dont la référence ne change pas
4    this.dinosaurs = []; // le contenu du tableau peut changer, mais pas la référence
5    this.config = {     // le contenu de l'objet peut changer, mais pas la référence
6      maxDinosaurs: 50,
7      securityLevel: "high"
8    };
9  }
10
11  addDinosaur(name, species, dangerLevel) {
12    // const pour les paramètres de fonction et les valeurs locales qui ne changent pas
13    const newDinosaur = {
14      id: Date.now(),
15      name,
16      species,
17      dangerLevel,
18      status: "active"
19    };
20
21    this.dinosaurs.push(newDinosaur);
22    return newDinosaur;
23  }
24
25  getDangerousCount() {
26    // let pour l'accumulateur
27    let count = 0;
28
29    // const dans for...of quand on ne modifie pas la variable de boucle
30    for (const dino of this.dinosaurs) {
31      if (dino.dangerLevel > 7) {
32        count++; // on modifie count, d'où let
33      }
34    }
35
36    return count;
37  }
38
39  calculateDailyFoodCost() {
40    // let pour les valeurs qui changent
41    let totalCost = 0;
42
43    // let dans une boucle for classique
44    for (let i = 0; i < this.dinosaurs.length; i++) {
45      const dino = this.dinosaurs[i]; // const - on ne réassigne pas la référence
46
47      // const pour les valeurs calculées
48      const dailyCost = this.calculateDinosaurFoodCost(dino);
49      totalCost += dailyCost;
50    }
51
52    return totalCost;
53  }
54
55  calculateDinosaurFoodCost(dinosaur) {
56    // const pour toutes les valeurs qui ne changent pas
57    const baseCost = 50;
58    const costMultiplier = dinosaur.dangerLevel > 5 ? 2 : 1;
59    const dailyCost = baseCost * costMultiplier;
60
61    return dailyCost;
62  }
63}
64
65// Exemple d'utilisation
66const parkSystem = new DinosaurManagementSystem(); // const - la référence ne change pas
67
68parkSystem.addDinosaur("Rexy", "T-Rex", 9);
69parkSystem.addDinosaur("Blue", "Velociraptor", 8);
70parkSystem.addDinosaur("Trixie", "Triceratops", 3);
71
72// let pour les valeurs qui vont changer
73let dangerousCount = parkSystem.getDangerousCount();
74console.log(`Dinosaures dangereux : ${dangerousCount}`);
75
76// const pour les valeurs à usage unique
77const foodCost = parkSystem.calculateDailyFoodCost();
78console.log(`Coût quotidien de nourriture : ${foodCost} $`);
79
80// On peut ajouter d'autres dinosaures
81parkSystem.addDinosaur("Delta", "Velociraptor", 8);
82
83// Et recalculer
84dangerousCount = parkSystem.getDangerousCount(); // let autorise la réassignation
85console.log(`Dinosaures dangereux : ${dangerousCount}`);

Règle de pouce

  1. Commence toujours par const - si le compilateur/linter se plaint que tu essaies de changer la valeur, passe à let
  2. N'utilise let que lorsque c'est nécessaire - boucles, compteurs, accumulateurs
  3. N'utilise jamais var - sauf pour éditer du vieux code
1// Bonne pratique
2const maxDinosaurs = 50;
3let currentDinosaurs = 0;
4
5for (let i = 0; i < maxDinosaurs; i++) {
6  const dinosaur = createDinosaur();
7  currentDinosaurs++;
8}
9
10// Mauvaise pratique
11var maxDinosaurs = 50;
12var currentDinosaurs = 0;
13
14for (var i = 0; i < maxDinosaurs; i++) {
15  var dinosaur = createDinosaur();
16  currentDinosaurs++;
17}

Pourquoi est-ce important ?

Tout comme des clôtures adaptées au Parc Jurassique empêchent les dinosaures de s'échapper, un bon usage de const et let prévient les bugs dans le code :

  1. Lisibilité - on voit immédiatement quelles valeurs changent (let) et lesquelles non (const)
  2. Sécurité - const protège contre les modifications accidentelles
  3. Portée de bloc - les variables ne « fuient » pas hors des blocs
  4. Débogage plus simple - moins de surprises dues au hoisting

Souviens-toi : en JavaScript moderne, var est comme les dinosaures - une relique intéressante du passé, mais que tu ne veux pas dans ton code de production !

Résumé

Le modèle d'événements de JavaScript (event loop) est la clé pour comprendre comment ce langage mono-thread peut gérer de nombreuses opérations asynchrones - indispensable dans des applications comme le système de gestion du Parc Jurassique.

Retiens les principes clés :

  1. JavaScript est mono-thread mais asynchrone
  2. L'event loop permet de gérer de nombreuses tâches sans bloquer l'interface utilisateur
  3. Les tâches s'exécutent dans un ordre défini : synchrone → microtasks → macrotasks
  4. Évite de bloquer l'event loop avec de longues opérations synchrones

Dans les prochaines leçons, nous verrons comment utiliser ces connaissances pour écrire du code asynchrone efficace à l'aide des callbacks, des Promises et de async/await.

Code de cette leçon : index.js
1// Event Loop and asynchronicity in Jurassic Park
2console.log("Centre de Contrôle du Parc Jurassique - Event Loop Demo");
3console.log("Simulation of asynchronous park management system\n");
4
5// ===========================================
6// 1. Les bases de l'Event Loop - Call Stack
7// ===========================================
8console.log("=== 1. CALL STACK - Execution order ===");
9
10function checkFence() {
11  console.log("1. Vérification de la clôture du T-Rex...");
12  return "Clôture sécurisée";
13}
14
15function checkDinosaur() {
16  console.log("2. Vérification de l'état du dinosaure...");
17  const result = checkFence(); // Synchronous call
18  console.log("3. " + result);
19  return "Dinosaure sécurisé";
20}
21
22function reportBezpieczenstwa() {
23  console.log("0. Starting security report");
24  const status = checkDinosaur();
25  console.log("4. Report: " + status);
26}
27
28// Exécution synchrone - call stack
29reportBezpieczenstwa();
30
31// ===========================================
32// 2. setTimeout - Callback Queue
33// ===========================================
34console.log("\n=== 2. SETTIMEOUT - Asynchronous operations ===");
35
36console.log("A. Je démarre la surveillance du parc...");
37
38setTimeout(() => {
39  console.log("C. Alarme ! Mouvement détecté dans l'enclos du T-Rex !");
40}, 2000);
41
42setTimeout(() => {
43  console.log("D. Système automatique : Sécurités supplémentaires activées");
44}, 1000);
45
46console.log("B. Continuing other tasks...");
47
48// ===========================================
49// 3. Park sensor simulation
50// ===========================================
51console.log("\n=== 3. PARK SENSOR SIMULATION ===");
52
53class DinosaurSensor {
54  constructor(name, dinosaur) {
55    this.name = name;
56    this.dinosaur = dinosaur;
57    this.isActive = true;
58  }
59
60  checkStatus(callback) {
61    console.log(`Sensor ${this.name}: Checking ${this.dinosaur}...`);
62
63    // Simulating sensor delay
64    const delay = Math.random() * 3000 + 1000; // 1-4 secondes
65
66    setTimeout(() => {
67      const status = Math.random() > 0.8 ? 'ALARM' : 'SÛR';
68      const report = {
69        czujnik: this.name,
70        dinosaur: this.dinosaur,
71        status: status,
72        czas: new Date().toLocaleTimeString()
73      };
74      callback(report);
75    }, delay);
76  }
77}
78
79// Callback hell - problem with nested callbacks
80function monitorPark() {
81  console.log("\nJe démarre le monitoring du parc...");
82
83  const czujnikTRex = new DinosaurSensor("TR-001", "T-Rex");
84  const czujnikVelociraptor = new DinosaurSensor("VR-002", "Velociraptor");
85  const czujnikTriceratops = new DinosaurSensor("TC-003", "Triceratops");
86
87  // Callback hell - each sensor one by one
88  czujnikTRex.checkStatus((reportTRex) => {
89    console.log("T-Rex report:", reportTRex);
90
91    czujnikVelociraptor.checkStatus((reportVelo) => {
92      console.log("Velociraptor report:", reportVelo);
93
94      czujnikTriceratops.checkStatus((reportTrice) => {
95        console.log("Triceratops report:", reportTrice);
96
97        // Analysis of all reports
98        const reporty = [reportTRex, reportVelo, reportTrice];
99        const alarms = reporty.filter(r => r.status === 'ALARM');
100
101        if (alarms.length > 0) {
102          console.log("WARNING! Alarms detected:", alarms.length);
103          alarms.forEach(alarm => {
104            console.log(`  ${alarm.dinosaur} - ${alarm.status}`);
105          });
106        } else {
107          console.log("All dinosaurs safe");
108        }
109      });
110    });
111  });
112}
113
114// Lance le monitoring après 3 secondes
115setTimeout(monitorPark, 3000);
116
117// ===========================================
118// 4. Promises - solving Callback Hell
119// ===========================================
120console.log("\n=== 4. PROMISES - Better asynchronous management ===");
121
122class ModernSensor {
123  constructor(name, dinosaur) {
124    this.name = name;
125    this.dinosaur = dinosaur;
126  }
127
128  checkStatus() {
129    return new Promise((resolve, reject) => {
130      console.log(`Promise: Checking ${this.dinosaur}...`);
131
132      const delay = Math.random() * 2000 + 500;
133
134      setTimeout(() => {
135        const success = Math.random() > 0.1; // 90% de chances de succès
136
137        if (success) {
138          const status = Math.random() > 0.7 ? 'ALARM' : 'SÛR';
139          resolve({
140            czujnik: this.name,
141            dinosaur: this.dinosaur,
142            status: status,
143            czas: new Date().toLocaleTimeString()
144          });
145        } else {
146          reject(new Error(`Sensor ${this.name} ne répond pas`));
147        }
148      }, delay);
149    });
150  }
151}
152
153function monitorParkPromises() {
154  console.log("\nPromise-based monitoring...");
155
156  const modernTRex = new ModernSensor("MTR-001", "Modern T-Rex");
157  const modernVelo = new ModernSensor("MVR-002", "Modern Velociraptor");
158  const modernTrice = new ModernSensor("MTC-003", "Modern Triceratops");
159
160  // Promise chain
161  modernTRex.checkStatus()
162    .then(report => {
163      console.log("Promise T-Rex:", report);
164      return modernVelo.checkStatus();
165    })
166    .then(report => {
167      console.log("Promise Velociraptor:", report);
168      return modernTrice.checkStatus();
169    })
170    .then(report => {
171      console.log("Promise Triceratops:", report);
172      console.log("Tous les capteurs Promise vérifiés !");
173    })
174    .catch(error => {
175      console.log("Error in Promise chain:", error.message);
176    });
177
178  // Promise.all - all at once
179  console.log("\nPromise.all - parallel checking...");
180
181  Promise.all([
182    modernTRex.checkStatus(),
183    modernVelo.checkStatus(),
184    modernTrice.checkStatus()
185  ])
186  .then(reporty => {
187    console.log("Tous les rapports Promise.all prêts :");
188    reporty.forEach(report => {
189      console.log(`  ${report.dinosaur}: ${report.status}`);
190    });
191
192    const alarms = reporty.filter(r => r.status === 'ALARM');
193    if (alarms.length > 0) {
194      console.log(`Promise.all: ${alarms.length} alarms!`);
195    } else {
196      console.log("Promise.all: Park safe!");
197    }
198  })
199  .catch(error => {
200    console.log("Promise.all error:", error.message);
201  });
202}
203
204// Lance le monitoring Promise après 8 secondes
205setTimeout(monitorParkPromises, 8000);
206
207// ===========================================
208// 5. Async/Await - cleanest approach
209// ===========================================
210console.log("\n=== 5. ASYNC/AWAIT - JavaScript asynchrone moderne ===");
211
212class AsyncSensor {
213  constructor(name, dinosaur) {
214    this.name = name;
215    this.dinosaur = dinosaur;
216  }
217
218  async checkStatus() {
219    console.log(`Async: Checking ${this.dinosaur}...`);
220
221    const delay = Math.random() * 1500 + 500;
222
223    // Async/await avec Promise
224    return new Promise((resolve, reject) => {
225      setTimeout(() => {
226        const success = Math.random() > 0.05; // 95% de succès
227
228        if (success) {
229          const status = Math.random() > 0.6 ? 'ALARM' : 'SÛR';
230          resolve({
231            czujnik: this.name,
232            dinosaur: this.dinosaur,
233            status: status,
234            czas: new Date().toLocaleTimeString(),
235            delay: delay.toFixed(0) + 'ms'
236          });
237        } else {
238          reject(new Error(`Erreur du capteur Async ${this.name}`));
239        }
240      }, delay);
241    });
242  }
243}
244
245// Async function
246async function monitorParkAsync() {
247  console.log("\nAsync/Await monitoring started...");
248
249  try {
250    const asyncTRex = new AsyncSensor("ATR-001", "Async T-Rex");
251    const asyncVelo = new AsyncSensor("AVR-002", "Async Velociraptor");
252    const asyncTrice = new AsyncSensor("ATC-003", "Async Triceratops");
253
254    // Séquentiellement (l'un après l'autre)
255    console.log("Sequential checking:");
256    const reportTRex = await asyncTRex.checkStatus();
257    console.log("  Async T-Rex:", reportTRex);
258
259    const reportVelo = await asyncVelo.checkStatus();
260    console.log("  Async Velociraptor:", reportVelo);
261
262    const reportTrice = await asyncTrice.checkStatus();
263    console.log("  Async Triceratops:", reportTrice);
264
265    // Parallel with Promise.all
266    console.log("\nParallel checking async/await:");
267    const [reportT2, reportV2, reportTr2] = await Promise.all([
268      asyncTRex.checkStatus(),
269      asyncVelo.checkStatus(),
270      asyncTrice.checkStatus()
271    ]);
272
273    const allReports = [reportT2, reportV2, reportTr2];
274    console.log("Parallel results:");
275    allReports.forEach(report => {
276      console.log(`  ${report.dinosaur}: ${report.status} (${report.delay})`);
277    });
278
279    // Security analysis
280    const dangerous = allReports.filter(r => r.status === 'ALARM');
281    if (dangerous.length > 0) {
282      console.log(`\nASYNC ALERT: ${dangerous.length} dinosaurs need attention!`);
283      dangerous.forEach(dino => {
284        console.log(`  ${dino.dinosaur} - intervention required`);
285      });
286    } else {
287      console.log("\nASYNC SUCCESS: All dinosaurs under control!");
288    }
289
290  } catch (error) {
291    console.log("Async/await error:", error.message);
292  }
293}
294
295// Lance le monitoring async après 15 secondes
296setTimeout(monitorParkAsync, 15000);
297
298// ===========================================
299// 6. L'Event Loop en pratique - Microtasks vs Macrotasks
300// ===========================================
301console.log("\n=== 6. EVENT LOOP - Execution order ===");
302
303function demonstracjaEventLoop() {
304  console.log("\nEvent Loop demo - execution order:");
305
306  console.log("1. Code synchrone");
307
308  setTimeout(() => {
309    console.log("4. setTimeout (Macro task)");
310  }, 0);
311
312  Promise.resolve().then(() => {
313    console.log("3. Promise.then (Micro task)");
314  });
315
316  console.log("2. Code synchrone - fin");
317
318  // Order: 1, 2, 3, 4
319  // Les microtasks (Promise) ont une priorité plus élevée que les macrotasks (setTimeout)
320}
321
322demonstracjaEventLoop();
323
324// ===========================================
325// 7. Practical example - Park alert system
326// ===========================================
327console.log("\n=== 7. PARK ALERT SYSTEM ===");
328
329class ParkAlertSystem {
330  constructor() {
331    this.alerts = [];
332    this.isMonitoring = false;
333  }
334
335  async startMonitoring() {
336    this.isMonitoring = true;
337    console.log("Alert system started monitoring...");
338
339    while (this.isMonitoring) {
340      await this.checkSystems();
341      await this.czekaj(2000); // Vérifie toutes les 2 secondes
342    }
343  }
344
345  stopMonitoring() {
346    this.isMonitoring = false;
347    console.log("⏹Monitoring stopped");
348  }
349
350  async checkSystems() {
351    const systemy = ['fence', 'alimentation', 'communication', 'security'];
352
353    const obietnice = systemy.map(system => this.checkSystem(system));
354
355    try {
356      const results = await Promise.allSettled(obietnice);
357
358      results.forEach((result, index) => {
359        const systemName = systemy[index];
360        if (result.status === 'fulfilled') {
361          if (result.value.status === 'ALARM') {
362            this.addAlert(systemName, result.value.message);
363          }
364        } else {
365          this.addAlert(systemName, `System error: ${result.reason}`);
366        }
367      });
368
369      if (this.alerts.length > 0) {
370        this.displayAlertsAndClear();
371      }
372
373    } catch (error) {
374      console.log("Critical monitoring error:", error);
375    }
376  }
377
378  async checkSystem(system) {
379    const delay = Math.random() * 1000 + 200;
380
381    return new Promise((resolve, reject) => {
382      setTimeout(() => {
383        const isWorking = Math.random() > 0.2; // 80% chance of working
384
385        if (isWorking) {
386          const hasAlert = Math.random() > 0.8; // 20% de chances d'alerte
387          resolve({
388            system,
389            status: hasAlert ? 'ALARM' : 'OK',
390            message: hasAlert ? `Problem with system ${system}` : `System ${system} is working correctly`
391          });
392        } else {
393          reject(`System ${system} ne répond pas`);
394        }
395      }, delay);
396    });
397  }
398
399  addAlert(system, message) {
400    this.alerts.push({
401      system,
402      message,
403      time: new Date().toLocaleTimeString()
404    });
405  }
406
407  displayAlertsAndClear() {
408    console.log(`\nPARK ALERT (${this.alerts.length} problems):`);
409    this.alerts.forEach(alert => {
410      console.log(`  [${alert.time}] ${alert.system}: ${alert.message}`);
411    });
412    this.alerts = []; // Clear after display
413  }
414
415  async czekaj(ms) {
416    return new Promise(resolve => setTimeout(resolve, ms));
417  }
418}
419
420// Alert system test
421const alertSystem = new ParkAlertSystem();
422
423// Lance le système après 20 secondes, pour 10 secondes
424setTimeout(async () => {
425  await alertSystem.startMonitoring();
426
427  // Arrête après 10 secondes
428  setTimeout(() => {
429    alertSystem.stopMonitoring();
430  }, 10000);
431}, 20000);
432
433// ===========================================
434// 8. Résumé de l'Event Loop
435// ===========================================
436setTimeout(() => {
437  console.log("\n" + "=".repeat(50));
438  console.log("RÉSUMÉ EVENT LOOP :");
439  console.log("1. JavaScript is single-threaded");
440  console.log("2. Event Loop permet l'asynchronicité");
441  console.log("3. Callbacks → Promises → Async/Await");
442  console.log("4. Les microtasks ont une priorité plus élevée que les macrotasks");
443  console.log("5. Async/Await est la syntaxe la plus propre");
444  console.log("=".repeat(50));
445}, 35000);

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. Qu'est-ce que l'Event Loop en JavaScript ?

Articles utiles