Cours JavaScript et TypeScript · Module 4: JavaScript asynchrone

Méthodes statiques de Promise (all, race, allSettled, any)

18 min de lecture
Dans cette leçon8

Lorsqu'on gère un parc rempli de dinosaures, il faut souvent surveiller plusieurs systèmes simultanément - clôtures, caméras, capteurs, équipes de sécurité. En JavaScript, on gère plusieurs opérations asynchrones parallèles à l'aide des méthodes statiques de l'objet Promise.

Promise.all() - tout doit réussir

Promise.all() prend un itérable de promises et retourne une seule Promise qui :

  • Se résout quand toutes les promises d'entrée se résolvent - avec un tableau de leurs valeurs (dans le même ordre)
  • Se rejette immédiatement dès que l'une des promises d'entrée se rejette - avec cette raison de rejet
1// Surveillance simultanée de trois systèmes critiques du parc
2function checkFenceSystem(sectorId) {
3  return new Promise((resolve, reject) => {
4    setTimeout(() => {
5      const voltage = 8000 + Math.random() * 4000;
6      if (voltage < 9000) {
7        reject(new Error(`Tension de clôture trop basse dans le secteur ${sectorId} : ${voltage.toFixed(0)}V`));
8      } else {
9        resolve({ sector: sectorId, voltage: voltage.toFixed(0), status: "ok" });
10      }
11    }, 500 + Math.random() * 1000);
12  });
13}
14
15function checkCameraSystem(zoneId) {
16  return new Promise(resolve => {
17    setTimeout(() => {
18      resolve({ zone: zoneId, camerasOnline: 12, camerasTotal: 12, status: "ok" });
19    }, 400 + Math.random() * 800);
20  });
21}
22
23function checkMotionSensors(areaId) {
24  return new Promise((resolve, reject) => {
25    setTimeout(() => {
26      const sensorsOnline = Math.floor(8 + Math.random() * 3);
27      if (sensorsOnline < 8) {
28        reject(new Error(`Trop peu de capteurs en ligne dans la zone ${areaId} : ${sensorsOnline}/10`));
29      } else {
30        resolve({ area: areaId, sensorsOnline, sensorsTotal: 10, status: "ok" });
31      }
32    }, 600 + Math.random() * 900);
33  });
34}
35
36// Tous les systèmes doivent être au vert avant d'ouvrir le parc
37Promise.all([
38  checkFenceSystem("A"),
39  checkCameraSystem("Nord"),
40  checkMotionSensors("Enclos-T-Rex")
41])
42  .then(([fenceResult, cameraResult, sensorResult]) => {
43    // Déstructuration du tableau de résultats
44    console.log("Tous les systèmes au vert - ouverture du parc en toute sécurité !");
45    console.log(`Clôture secteur A : ${fenceResult.voltage}V`);
46    console.log(`Caméras Nord : ${cameraResult.camerasOnline}/${cameraResult.camerasTotal} en ligne`);
47    console.log(`Capteurs Enclos T-Rex : ${sensorResult.sensorsOnline}/${sensorResult.sensorsTotal} en ligne`);
48  })
49  .catch(error => {
50    console.error(`OUVERTURE DU PARC BLOQUÉE : ${error.message}`);
51    console.log("Équipe de maintenance dépêchée.");
52  });

Exemple complet : vérification de l'état d'ouverture du parc

Avant que le Parc Jurassique puisse ouvrir aux visiteurs, nous devons vérifier divers systèmes simultanément :

1// Fonctions retournant des Promises pour différents systèmes
2function checkSecuritySystems() {
3  console.log("Vérification des systèmes de sécurité...");
4  return new Promise((resolve, reject) => {
5    setTimeout(() => {
6      const systemsOperational = Math.random() > 0.1; // 10% de chance d'erreur
7      if (systemsOperational) {
8        resolve({
9          status: "OK",
10          message: "Tous les systèmes de sécurité fonctionnent correctement",
11          lastChecked: new Date().toISOString()
12        });
13      } else {
14        reject(new Error("La clôture d'urgence du secteur B ne fonctionne pas !"));
15      }
16    }, 2000);
17  });
18}
19
20function checkDinosaurHealthStatus() {
21  console.log("Vérification de la santé des dinosaures...");
22  return new Promise((resolve) => {
23    setTimeout(() => {
24      resolve({
25        status: "OK",
26        message: "Tous les dinosaures en bonne santé",
27        count: 36,
28        sick: 0
29      });
30    }, 3000);
31  });
32}
33
34function checkPowerSystems() {
35  console.log("Vérification des systèmes d'alimentation...");
36  return new Promise((resolve) => {
37    setTimeout(() => {
38      resolve({
39        status: "OK",
40        message: "Alimentation principale et de secours fonctionnant correctement",
41        mainPower: "online",
42        backupPower: "standby"
43      });
44    }, 1500);
45  });
46}
47
48function checkWeather() {
49  console.log("Vérification des prévisions météo...");
50  return new Promise((resolve, reject) => {
51    setTimeout(() => {
52      const weatherOK = Math.random() > 0.2; // 20% de chance de mauvais temps
53      if (weatherOK) {
54        resolve({
55          status: "OK",
56          message: "Météo favorable",
57          forecast: "Ensoleillé, 25°C"
58        });
59      } else {
60        reject(new Error("Tempête tropicale en approche ! Ouverture du parc impossible."));
61      }
62    }, 1000);
63  });
64}
65
66// Fonction principale de vérification de l'état du parc
67function checkParkReadiness() {
68  console.log("Démarrage de la vérification de l'état du parc pour l'ouverture...");
69
70  const startTime = Date.now();
71
72  // Lancer toutes les vérifications en parallèle
73  const security = checkSecuritySystems();
74  const dinosaurs = checkDinosaurHealthStatus();
75  const power = checkPowerSystems();
76  const weather = checkWeather();
77
78  // Utiliser Promise.all pour attendre tous les résultats
79  return Promise.all([security, dinosaurs, power, weather])
80    .then(results => {
81      const endTime = Date.now();
82      const executionTime = (endTime - startTime) / 1000;
83
84      // Décomposer les résultats
85      const [securityResult, dinosaursResult, powerResult, weatherResult] = results;
86
87      return {
88        status: "PARC PRÊT À OUVRIR",
89        security: securityResult,
90        dinosaurs: dinosaursResult,
91        power: powerResult,
92        weather: weatherResult,
93        checkDuration: `${executionTime} secondes`
94      };
95    })
96    .catch(error => {
97      throw new Error(`Le parc n'est pas prêt à ouvrir : ${error.message}`);
98    });
99}
100
101// Utilisation de notre fonction
102checkParkReadiness()
103  .then(report => {
104    console.log("RAPPORT D'ÉTAT DU PARC :");
105    console.log(JSON.stringify(report, null, 2));
106  })
107  .catch(error => {
108    console.error("ERREUR :", error.message);
109  });

Dans l'exemple ci-dessus :

  1. Nous lançons 4 vérifications indépendantes des systèmes du parc en parallèle
  2. Nous utilisons Promise.all() pour attendre la fin de toutes les vérifications
  3. Si toutes réussissent, nous obtenons un tableau de résultats dans le même ordre que le tableau de promises
  4. Si l'une échoue, toute l'opération est considérée comme échouée

Remarque que toutes les opérations sont lancées simultanément (et non l'une après l'autre), ce qui accélère considérablement l'ensemble du processus. Au lieu d'attendre 2000 + 3000 + 1500 + 1000 = 7500 ms, nous n'attendons que l'opération la plus longue, soit environ 3000 ms.

Traiter les résultats de Promise.all()

Le résultat d'une promise résolue retournée par Promise.all() est un tableau de résultats dans le même ordre que le tableau de promises d'entrée. Nous pouvons utiliser la déstructuration de tableau pour attribuer élégamment des noms aux résultats individuels :

1Promise.all([fetchDino(1), fetchDino(2), fetchDino(3)])
2  .then(([dino1, dino2, dino3]) => {
3    console.log("T-Rex :", dino1.name);
4    console.log("Velociraptor :", dino2.name);
5    console.log("Triceratops :", dino3.name);
6  });

Quand ne pas utiliser Promise.all()

Souviens-toi que Promise.all() adopte une approche « tout ou rien ». Si une promise est rejetée, toute l'opération échoue. Ce n'est pas toujours le comportement souhaité.

Tableaux dynamiques avec Promise.all()

1const sectors = ["A", "B", "C", "D", "E"];
2
3// Mapper les secteurs en promises, puis attendre toutes
4Promise.all(sectors.map(sector => checkFenceSystem(sector)))
5  .then(results => {
6    const totalVoltage = results.reduce((sum, r) => sum + Number(r.voltage), 0);
7    console.log(`Les ${results.length} secteurs de clôture sont opérationnels`);
8    console.log(`Tension moyenne des clôtures : ${(totalVoltage / results.length).toFixed(0)}V`);
9  })
10  .catch(error => {
11    console.error("Panne du système de clôture :", error.message);
12  });

Promise.allSettled() - continuer malgré les échecs

Promise.allSettled() prend un itérable de promises et retourne une Promise qui se résout toujours (ne se rejette jamais) une fois que toutes les promises se sont terminées. Chaque résultat est un objet avec :

  • { status: "fulfilled", value: ... } pour les promises résolues
  • { status: "rejected", reason: ... } pour les promises rejetées
1const monitoringTasks = [
2  checkFenceSystem("A"),
3  checkFenceSystem("B"),
4  checkCameraSystem("Est"),
5  checkMotionSensors("Enclos-Raptors"),
6  checkMotionSensors("Vallée-Herbivores")
7];
8
9// On veut un rapport d'état complet même si certains systèmes échouent
10Promise.allSettled(monitoringTasks)
11  .then(results => {
12    const successful = results.filter(r => r.status === "fulfilled");
13    const failed = results.filter(r => r.status === "rejected");
14
15    console.log(`=== Rapport d'état du parc ===`);
16    console.log(`Systèmes OK : ${successful.length} / ${results.length}`);
17    console.log(`Systèmes EN PANNE : ${failed.length} / ${results.length}`);
18
19    if (failed.length > 0) {
20      console.log("\nPannes :");
21      failed.forEach(r => console.log(`  - ${r.reason.message}`));
22    }
23
24    // Décider d'ouvrir ou non selon le nombre de pannes
25    if (failed.length === 0) {
26      console.log("\nDécision : OUVRIR - tous les systèmes nominaux");
27    } else if (failed.length <= 1) {
28      console.log("\nDécision : OUVRIR AVEC PRUDENCE - problèmes mineurs détectés");
29    } else {
30      console.log("\nDécision : FERMÉ - trop de pannes système");
31    }
32  });

Promise.allSettled() vs. Promise.all()

Comparons le comportement de Promise.allSettled() avec celui de Promise.all() :

1// Un tableau contenant des promises, dont certaines sont rejetées
2const mixedPromises = [
3  Promise.resolve("Succès 1"),
4  Promise.reject(new Error("Erreur 1")),
5  Promise.resolve("Succès 2"),
6  Promise.reject(new Error("Erreur 2"))
7];
8
9// Utilisation de Promise.all
10Promise.all(mixedPromises)
11  .then(results => {
12    console.log("Toutes réussies :", results);
13    // Ce code ne s'exécutera jamais à cause des Promises rejetées dans le tableau
14  })
15  .catch(error => {
16    console.error("Promise.all a intercepté une erreur :", error.message);
17    // Affiche : "Promise.all a intercepté une erreur : Erreur 1"
18    // Remarque qu'on n'obtient que la première erreur, pas toutes
19  });
20
21// Utilisation de Promise.allSettled
22Promise.allSettled(mixedPromises)
23  .then(results => {
24    console.log("Résultats de toutes les Promises :");
25
26    // Traiter tous les résultats
27    results.forEach((result, index) => {
28      if (result.status === 'fulfilled') {
29        console.log(`Promise ${index} : OK - ${result.value}`);
30      } else {
31        console.log(`Promise ${index} : ERREUR - ${result.reason.message}`);
32      }
33    });
34
35    // Affiche :
36    // "Promise 0 : OK - Succès 1"
37    // "Promise 1 : ERREUR - Erreur 1"
38    // "Promise 2 : OK - Succès 2"
39    // "Promise 3 : ERREUR - Erreur 2"
40  });

La différence clé : Promise.all() échoue à la première erreur, tandis que Promise.allSettled() réussit toujours avec des informations sur tous les résultats.

Promise.race() - le premier à terminer gagne

Promise.race() retourne une Promise qui se termine dès qu'une promise d'entrée se termine - qu'elle se résolve ou se rejette. Le « gagnant » détermine le résultat.

C'est une solution idéale pour les scénarios où :

  • Tu n'as besoin que du premier résultat disponible
  • Tu veux fixer un délai d'attente (timeout) pour une opération
  • Tu dois implémenter un pattern de « course »

Reconnaissance rapide d'espèce de dinosaure

Imagine que nous avons plusieurs systèmes de reconnaissance d'espèces de dinosaures (visuel, ADN, comportemental) et que nous voulons utiliser le résultat de celui qui fonctionne le plus vite :

1function recognizeVisually(dinosaurImage) {
2  console.log("Démarrage de l'analyse visuelle...");
3  return new Promise((resolve, reject) => {
4    setTimeout(() => {
5      if (Math.random() > 0.3) { // 70% de chance de succès
6        resolve({
7          method: "visual",
8          species: "Velociraptor",
9          confidence: 0.82,
10          timeMs: 1800
11        });
12      } else {
13        reject(new Error("Échec de la reconnaissance visuelle"));
14      }
15    }, 1800);
16  });
17}
18
19function recognizeByDNA(dnaSample) {
20  console.log("Démarrage de l'analyse ADN...");
21  return new Promise((resolve, reject) => {
22    setTimeout(() => {
23      if (Math.random() > 0.1) { // 90% de chance de succès
24        resolve({
25          method: "dna",
26          species: "Velociraptor",
27          subspecies: "V. mongoliensis",
28          confidence: 0.98,
29          timeMs: 3500
30        });
31      } else {
32        reject(new Error("Échantillon d'ADN contaminé"));
33      }
34    }, 3500);
35  });
36}
37
38function recognizeBehavior(behavioralRecording) {
39  console.log("Démarrage de l'analyse comportementale...");
40  return new Promise((resolve, reject) => {
41    setTimeout(() => {
42      if (Math.random() > 0.4) { // 60% de chance de succès
43        resolve({
44          method: "behavioral",
45          species: "Velociraptor",
46          behaviors: ["pack-hunting", "high-agility", "vocalizations"],
47          confidence: 0.75,
48          timeMs: 2200
49        });
50      } else {
51        reject(new Error("Données comportementales insuffisantes"));
52      }
53    }, 2200);
54  });
55}
56
57// Fonction de reconnaissance rapide de dinosaure
58function quickRecognition(data) {
59  console.log("Démarrage de la reconnaissance rapide du dinosaure...");
60
61  return Promise.race([
62    recognizeVisually(data.image),
63    recognizeByDNA(data.dna),
64    recognizeBehavior(data.recording)
65  ]);
66}
67
68// Utilisation
69const dinosaurData = {
70  image: "https://jurassic-world.com/images/suspect_dino.jpg",
71  dna: "ACGTTTCGAACGTACGATCGATCG...",
72  recording: "https://jurassic-world.com/videos/suspect_dino.mp4"
73};
74
75quickRecognition(dinosaurData)
76  .then(result => {
77    console.log(`Dinosaure reconnu comme : ${result.species}`);
78    console.log(`Méthode : ${result.method}, Confiance : ${result.confidence * 100}%`);
79    console.log(`Temps de reconnaissance : ${result.timeMs} ms`);
80  })
81  .catch(error => {
82    console.error("Échec de la reconnaissance du dinosaure :", error.message);
83  });

Dans cet exemple, nous lançons trois méthodes de reconnaissance en parallèle et utilisons le premier résultat disponible. La méthode visuelle est la plus rapide (1800 ms) mais la moins précise. La méthode ADN est la plus lente (3500 ms) mais la plus précise. La méthode comportementale se situe entre les deux. Grâce à Promise.race(), nous obtenons le premier résultat disponible, ce qui permet une réponse rapide.

Implémenter un timeout avec Promise.race()

Nous devons souvent interrompre les opérations longues si elles prennent trop de temps. Promise.race() est l'outil idéal pour implémenter un timeout :

1// Cas d'usage classique : pattern timeout
2function withTimeout(promise, timeoutMs, label) {
3  const timeout = new Promise((_, reject) =>
4    setTimeout(() => reject(new Error(`${label} a expiré après ${timeoutMs}ms`)), timeoutMs)
5  );
6  return Promise.race([promise, timeout]);
7}
8
9// Capteur lent avec une protection par timeout
10function readSlowSensor(sensorId) {
11  return new Promise(resolve => {
12    const delay = 1000 + Math.random() * 4000; // 1-5 secondes
13    setTimeout(() => resolve({ sensorId, reading: Math.random() * 100 }), delay);
14  });
15}
16
17withTimeout(readSlowSensor("SENSOR-42"), 2000, "Lecture du capteur 42")
18  .then(data => {
19    console.log(`Lecture du capteur : ${data.reading.toFixed(2)}`);
20  })
21  .catch(error => {
22    console.error(error.message);
23    // Utiliser une valeur en cache/par défaut à la place
24    console.log("Utilisation de la dernière valeur connue du capteur : 67.3");
25  });

Course pour le serveur le plus rapide

1// Essayer plusieurs miroirs d'API - utiliser celui qui répond en premier
2const apiMirrors = [
3  "https://api-us.jurassic-park.com",
4  "https://api-eu.jurassic-park.com",
5  "https://api-asia.jurassic-park.com"
6];
7
8function fetchFromMirror(url) {
9  return new Promise(resolve => {
10    const delay = 200 + Math.random() * 1800; // Latence variable selon la région
11    setTimeout(() => resolve({ url, data: { status: "active" }, latency: delay }), delay);
12  });
13}
14
15Promise.race(apiMirrors.map(url => fetchFromMirror(url)))
16  .then(result => {
17    console.log(`Miroir le plus rapide : ${result.url} (${result.latency.toFixed(0)}ms)`);
18  });

Promise.any() - la première réussite gagne

Promise.any() est similaire à race() mais ne se résout que sur la première promise résolue (ignore les rejets, sauf si toutes se rejettent, auquel cas elle se rejette avec une AggregateError).

1// Essayer plusieurs capteurs de secours - réussir dès que l'un répond
2function readSensorWithFallback(primaryId, backupIds) {
3  const allSensorPromises = [primaryId, ...backupIds].map(id =>
4    new Promise((resolve, reject) => {
5      setTimeout(() => {
6        if (Math.random() > 0.4) {
7          resolve({ sensorId: id, value: Math.random() * 100 });
8        } else {
9          reject(new Error(`Capteur ${id} hors ligne`));
10        }
11      }, 300 + Math.random() * 700);
12    })
13  );
14
15  return Promise.any(allSensorPromises);
16}
17
18readSensorWithFallback("TEMP-01", ["TEMP-02", "TEMP-03", "TEMP-04"])
19  .then(result => {
20    console.log(`Lecture obtenue du capteur ${result.sensorId} : ${result.value.toFixed(1)}°C`);
21  })
22  .catch(error => {
23    // AggregateError - tous les capteurs ont échoué
24    console.error("Tous les capteurs de température sont hors ligne :", error.message);
25    console.log("Déclenchement du protocole d'urgence de température...");
26  });

Tester les portes d'urgence

1function testEmergencyGate(id) {
2  console.log(`Test de la porte d'urgence n°${id}...`);
3  return new Promise((resolve, reject) => {
4    setTimeout(() => {
5      const works = Math.random() > 0.6; // 40% de chance d'échec
6      if (works) {
7        resolve({
8          id: id,
9          status: "Opérationnelle",
10          openingTime: `${(Math.random() * 2 + 1).toFixed(1)}s`
11        });
12      } else {
13        reject(new Error(`La porte n°${id} ne répond pas au signal d'ouverture`));
14      }
15    }, 1000 + Math.random() * 2000);
16  });
17}
18
19// Tenter d'ouvrir n'importe quelle porte d'urgence en état de marche
20function findWorkingGate() {
21  console.log("Tentative d'ouverture de n'importe quelle porte d'urgence en état de marche...");
22
23  // Tester toutes les portes simultanément
24  return Promise.any([
25    testEmergencyGate(1),
26    testEmergencyGate(2),
27    testEmergencyGate(3),
28    testEmergencyGate(4)
29  ]);
30}
31
32// Utilisation
33findWorkingGate()
34  .then(workingGate => {
35    console.log(`Porte d'urgence en état de marche trouvée : n°${workingGate.id}`);
36    console.log(`Temps d'ouverture : ${workingGate.openingTime}`);
37    console.log("Démarrage de l'évacuation par cette porte...");
38  })
39  .catch(error => {
40    // Ce catch ne s'exécute que lorsque TOUTES les portes échouent
41    console.error("ERREUR CRITIQUE : toutes les portes d'urgence sont hors service !");
42    console.error("Détails :", error.errors.map(e => e.message).join(", "));
43    console.log("Activation de la procédure de forçage des portes d'urgence...");
44  });

Dans cet exemple, Promise.any() nous permet de trouver la première porte d'urgence en état de marche et de commencer immédiatement l'évacuation par celle-ci, en ignorant les portes qui ne fonctionnent pas. Ce n'est que lorsque toutes les portes échouent que l'opération entière échoue.

Choisir la bonne méthode

1// Promise.all()   - TOUTES doivent réussir ; échoue vite si l'une échoue
2// À utiliser quand : on récupère des ressources TOUTES requises pour l'étape suivante
3const [dinoData, enclosureData, staffData] = await Promise.all([
4  fetchDinosaur(id),
5  fetchEnclosure(id),
6  fetchStaff(id)
7]);
8
9// Promise.allSettled() - collecte TOUS les résultats ; ne lève jamais d'erreur
10// À utiliser quand : on génère un rapport d'état, on lance des tâches indépendantes en arrière-plan
11const systemStatuses = await Promise.allSettled([
12  checkFences(), checkCameras(), checkSensors(), checkPower()
13]);
14
15// Promise.race()   - la première à se TERMINER (résoudre OU rejeter) gagne
16// À utiliser quand : on implémente des timeouts, on court contre une échéance
17const result = await Promise.race([fetchData(), timeout(3000)]);
18
19// Promise.any()   - la première à RÉSOUDRE gagne (ignore les rejets)
20// À utiliser quand : on essaie plusieurs sources et n'importe quel succès convient
21const reading = await Promise.any([sensor1(), sensor2(), sensor3()]);

Combinaison pratique - séquence complète de démarrage du parc

1async function startParkOperations() {
2  console.log("=== Parc Jurassique - Séquence de démarrage quotidienne ===\n");
3
4  // Phase 1 : Systèmes critiques - TOUS doivent être en ligne
5  console.log("Phase 1 : Vérification des systèmes critiques...");
6  try {
7    const [fences, power, locks] = await Promise.all([
8      checkFenceSystem("périmètre"),
9      checkPowerGrid(),
10      checkGateLocks()
11    ]);
12    console.log("Tous les systèmes critiques sont nominaux");
13  } catch (error) {
14    console.error("Panne d'un système critique :", error.message);
15    console.log("ANNULATION : impossible d'ouvrir le parc - système critique hors ligne");
16    return;
17  }
18
19  // Phase 2 : Systèmes secondaires - signaler l'état mais ne pas bloquer l'ouverture
20  console.log("\nPhase 2 : Vérification des systèmes secondaires...");
21  const secondaryResults = await Promise.allSettled([
22    checkCameraSystem("all"),
23    checkGiftShops(),
24    checkRestaurants(),
25    checkTransportation()
26  ]);
27
28  const failedSecondary = secondaryResults.filter(r => r.status === "rejected");
29  if (failedSecondary.length > 0) {
30    console.warn(` ${failedSecondary.length} systèmes secondaires nécessitent une attention`);
31    failedSecondary.forEach(r => console.warn(`   - ${r.reason.message}`));
32  } else {
33    console.log("Tous les systèmes secondaires sont prêts");
34  }
35
36  // Phase 3 : Course pour obtenir l'état du parc depuis la source de données la plus rapide
37  console.log("\nPhase 3 : Récupération des données des visiteurs...");
38  try {
39    const visitorData = await Promise.race([
40      fetchVisitorReservations("primary-db"),
41      fetchVisitorReservations("replica-db"),
42      timeout(2000)
43    ]);
44    console.log(`${visitorData.count} visiteurs attendus aujourd'hui`);
45  } catch (error) {
46    console.warn(" Impossible de récupérer les données des visiteurs - on continue sans elles");
47  }
48
49  console.log("\nLe parc est ouvert - bienvenue au Parc Jurassique !");
50}
51
52// Fonctions auxiliaires
53function checkPowerGrid() { return new Promise(res => setTimeout(() => res({ status: "ok" }), 400)); }
54function checkGateLocks() { return new Promise(res => setTimeout(() => res({ status: "locked" }), 300)); }
55function checkGiftShops() { return new Promise(res => setTimeout(() => res({ status: "ready" }), 600)); }
56function checkRestaurants() { return new Promise((_, rej) => setTimeout(() => rej(new Error("Équipement de cuisine du restaurant B hors ligne")), 500)); }
57function checkTransportation() { return new Promise(res => setTimeout(() => res({ status: "ready" }), 700)); }
58function fetchVisitorReservations(source) { return new Promise(res => setTimeout(() => res({ count: 1247, source }), 800 + Math.random() * 1200)); }
59function timeout(ms) { return new Promise((_, rej) => setTimeout(() => rej(new Error(`Timeout après ${ms}ms`)), ms)); }
60
61startParkOperations();

Pratiques avancées

Créer dynamiquement un tableau de Promises

Souvent, nous devons créer dynamiquement un tableau de promises à partir de données :

1// Récupération des données de tous les dinosaures du parc
2function fetchAllDinosaurData(ids) {
3  console.log(`Récupération des données de ${ids.length} dinosaures...`);
4
5  // Mapper les IDs en promises
6  const promises = ids.map(id => fetchDinosaurInfo(id));
7
8  // Utiliser Promise.all pour une récupération parallèle
9  return Promise.all(promises);
10}
11
12// Utilisation
13const dinosaurIds = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
14
15fetchAllDinosaurData(dinosaurIds)
16  .then(dinosaurs => {
17    console.log(`Données récupérées pour ${dinosaurs.length} dinosaures`);
18
19    // Calculer des statistiques
20    const carnivores = dinosaurs.filter(d => d.diet === "carnivore").length;
21    const herbivores = dinosaurs.filter(d => d.diet === "herbivore").length;
22    const omnivores = dinosaurs.filter(d => d.diet === "omnivore").length;
23
24    console.log(`Statistiques : ${carnivores} carnivores, ${herbivores} herbivores, ${omnivores} omnivores`);
25  })
26  .catch(error => {
27    console.error("Erreur lors de la récupération des données :", error.message);
28  });

Combiner différentes méthodes de composition de Promises

Nous pouvons combiner différentes méthodes de composition de Promises pour obtenir des comportements plus complexes :

1function monitorDinosaurs() {
2  // Récupérer tous les secteurs du parc
3  return fetchAllSectors()
4    .then(sectors => {
5      // Pour chaque secteur, vérifier les dinosaures en parallèle
6      const sectorPromises = sectors.map(sector => {
7        // Pour chaque secteur, utiliser allSettled pour avoir des infos sur tous les dinosaures
8        const dinosaurPromises = sector.dinosaurs.map(dino =>
9          checkDinosaurStatus(dino.id)
10            .catch(error => ({
11              status: "error",
12              dinoId: dino.id,
13              message: error.message
14            }))
15        );
16
17        // Utiliser allSettled pour les dinosaures d'un secteur
18        return Promise.allSettled(dinosaurPromises)
19          .then(results => ({
20            sector: sector.id,
21            name: sector.name,
22            results: results
23          }));
24      });
25
26      // Utiliser all pour les secteurs car nous avons besoin des données de tous
27      return Promise.all(sectorPromises);
28    });
29}

Gérer un trop grand nombre de requêtes parallèles

Lorsque nous avons de nombreuses opérations asynchrones à effectuer, ce n'est pas toujours une bonne idée de toutes les lancer simultanément - cela peut surcharger le système. Nous pouvons implémenter un mécanisme pour limiter le nombre d'opérations parallèles :

1// Fonction qui exécute des tâches avec un parallélisme limité
2async function executeTasksWithLimit(tasks, maxParallel) {
3  const results = [];
4
5  // Traiter les tâches par lots
6  for (let i = 0; i < tasks.length; i += maxParallel) {
7    const batch = tasks.slice(i, i + maxParallel);
8
9    // Exécuter le lot de tâches en parallèle
10    const batchResult = await Promise.allSettled(batch.map(task => task()));
11    results.push(...batchResult);
12
13    console.log(`Lot ${Math.floor(i / maxParallel) + 1}/${Math.ceil(tasks.length / maxParallel)} terminé`);
14  }
15
16  return results;
17}
18
19// Exemple d'utilisation
20const dinosaurTasks = Array.from({ length: 50 }, (_, i) => {
21  return () => checkDinosaurStatus(i + 1);
22});
23
24// Exécuter 5 tâches au maximum simultanément
25executeTasksWithLimit(dinosaurTasks, 5)
26  .then(results => {
27    console.log(`Vérification terminée pour les ${results.length} dinosaures`);
28    // Analyser les résultats...
29  });

Résumé

Les méthodes statiques de Promise (Promise.all(), Promise.race(), Promise.allSettled() et Promise.any()) offrent des outils puissants pour composer et coordonner plusieurs opérations asynchrones. Le choix de la bonne méthode dépend des besoins précis de ton application :

MéthodeSe résout quandSe rejette quandCas d'usage
Promise.all()TOUTES se résolventUNE se rejetteToutes requises
Promise.allSettled()TOUTES se terminentJamaisRapports d'état
Promise.race()La PREMIÈRE se termineLa PREMIÈRE se rejettePattern timeout
Promise.any()La PREMIÈRE se résoutTOUTES se rejettentSources de secours
  • Promise.all() - quand tu as besoin de tous les résultats et que toutes les opérations doivent réussir
  • Promise.race() - quand tu as besoin du premier résultat ou que tu veux implémenter un timeout
  • Promise.allSettled() - quand tu as besoin d'informations sur toutes les opérations, quel que soit le résultat
  • Promise.any() - quand tu as besoin du premier résultat réussi, en ignorant les erreurs

Au Parc Jurassique, comme dans toute application JavaScript complexe, la capacité à composer efficacement des opérations asynchrones est essentielle pour créer des systèmes réactifs, performants et fiables.

Dans le prochain exercice, nous apprendrons la syntaxe async/await, qui offre une façon encore plus lisible de travailler avec du code asynchrone.

Code de cette leçon : index.js
1// Scope Chain Explorer - Variable Scope
2console.log("VARIABLE SCOPE IN JURASSIC PARK\n");
3
4// Global scope
5const parkName = "Jurassic Park";
6let visitorCount = 0;
7
8function managePark() {
9  // Function scope
10  const parkStatus = "OPEN";
11  let securityLevel = 5;
12
13  console.log("=== PARK MANAGEMENT ===");
14  console.log(`Park: ${parkName}`); // Access to global
15  console.log(`Status: ${parkStatus}`);
16  console.log(`Security level: ${securityLevel}`);
17
18  function checkSecurity() {
19    // Nested function scope
20    const alertMessage = "Checking security...";
21
22    console.log(`\n${alertMessage}`);
23    console.log(`Park: ${parkName}`); // Access to global
24    console.log(`Park status: ${parkStatus}`); // Access to parent function
25    console.log(`Niveau: ${securityLevel}`); // Access to parent function
26
27    // Modification de la variable depuis le parent scope
28    if (securityLevel < 8) {
29      securityLevel = 8;
30      console.log(`Security level raised to: ${securityLevel}`);
31    }
32
33    function activateAlarm() {
34      // Even deeper nested function
35      const alarmCode = "RED";
36      console.log(`\nALARM ${alarmCode}!`);
37      console.log(`Park: ${parkName}`);
38      console.log(`Status: ${parkStatus}`);
39      console.log(`Security: ${securityLevel}`);
40
41      // Nous POUVONS accéder à alertMessage depuis checkSecurity
42      // console.log(alertMessage); // Cela fonctionnerait !
43    }
44
45    activateAlarm();
46  }
47
48  checkSecurity();
49  console.log(`\nNiveau après vérification: ${securityLevel}`); // Modifié par checkSecurity
50
51  // console.log(alertMessage); // ERROR! alertMessage est dans le function scope de checkSecurity
52}
53
54managePark();
55
56// console.log(parkStatus); // ERROR! parkStatus est dans le function scope de managePark
57
58console.log(`\n=== BLOCK SCOPE (let/const) ===\n`);
59
60if (true) {
61  let blockVariable = "Je suis dans le bloc";
62  const blockConstant = "Me too!";
63  var functionVariable = "Je suis var";
64
65  console.log(blockVariable);
66  console.log(blockConstant);
67  console.log(functionVariable);
68}
69
70// console.log(blockVariable); // ERROR! let a un block scope
71// console.log(blockConstant); // ERROR! const a un block scope
72console.log(functionVariable); // OK! var a un function scope (ou global)

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. Lequel des éléments suivants est une microtask ?

  2. 2. Que fait Promise.all() ?

Tâches pratiques dans le jeu

  • Éditeur de code

    Utilise Promise.all() pour exécuter trois opérations de surveillance en parallèle et afficher un rapport combiné une fois qu'elles sont toutes terminées.

  • Tri vertical

    Ordonne les mécanismes d'asynchronicité par ordre chronologique :

  • Clique dans l'ordre

    Ordonne les éléments de la gestion d'erreur dans une Promise :

Articles utiles