When managing a park full of dinosaurs, we often need to monitor multiple systems simultaneously — fences, cameras, sensors, security teams. In JavaScript, we handle multiple parallel asynchronous operations using the static methods of the Promise object.
Promise.all() takes an iterable of promises and returns a single Promise that:1// Monitoring three critical park systems simultaneously
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(`Fence voltage too low in sector ${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(`Too few sensors online in area ${areaId}: ${sensorsOnline}/10`));
29 } else {
30 resolve({ area: areaId, sensorsOnline, sensorsTotal: 10, status: "ok" });
31 }
32 }, 600 + Math.random() * 900);
33 });
34}
35
36// All systems must be green before opening the park
37Promise.all([
38 checkFenceSystem("A"),
39 checkCameraSystem("North"),
40 checkMotionSensors("T-Rex-Paddock")
41])
42 .then(([fenceResult, cameraResult, sensorResult]) => {
43 // Destructuring the results array
44 console.log("All systems green — safe to open the park!");
45 console.log(`Fence sector A: ${fenceResult.voltage}V`);
46 console.log(`Cameras North: ${cameraResult.camerasOnline}/${cameraResult.camerasTotal} online`);
47 console.log(`Sensors T-Rex Paddock: ${sensorResult.sensorsOnline}/${sensorResult.sensorsTotal} online`);
48 })
49 .catch(error => {
50 console.error(`PARK OPENING BLOCKED: ${error.message}`);
51 console.log("Maintenance team dispatched.");
52 });Before Jurassic Park can be opened for visitors, we need to check various systems simultaneously:
1// Functions returning Promises for different systems
2function checkSecuritySystems() {
3 console.log("Checking security systems...");
4 return new Promise((resolve, reject) => {
5 setTimeout(() => {
6 const systemsOperational = Math.random() > 0.1; // 10% chance of error
7 if (systemsOperational) {
8 resolve({
9 status: "OK",
10 message: "All security systems are operating correctly",
11 lastChecked: new Date().toISOString()
12 });
13 } else {
14 reject(new Error("Emergency fence in sector B is not working!"));
15 }
16 }, 2000);
17 });
18}
19
20function checkDinosaurHealthStatus() {
21 console.log("Checking dinosaur health...");
22 return new Promise((resolve) => {
23 setTimeout(() => {
24 resolve({
25 status: "OK",
26 message: "All dinosaurs healthy",
27 count: 36,
28 sick: 0
29 });
30 }, 3000);
31 });
32}
33
34function checkPowerSystems() {
35 console.log("Checking power systems...");
36 return new Promise((resolve) => {
37 setTimeout(() => {
38 resolve({
39 status: "OK",
40 message: "Main and backup power operating correctly",
41 mainPower: "online",
42 backupPower: "standby"
43 });
44 }, 1500);
45 });
46}
47
48function checkWeather() {
49 console.log("Checking weather forecast...");
50 return new Promise((resolve, reject) => {
51 setTimeout(() => {
52 const weatherOK = Math.random() > 0.2; // 20% chance of bad weather
53 if (weatherOK) {
54 resolve({
55 status: "OK",
56 message: "Favorable weather",
57 forecast: "Sunny, 25°C"
58 });
59 } else {
60 reject(new Error("Tropical storm approaching! Park opening impossible."));
61 }
62 }, 1000);
63 });
64}
65
66// Main function to check park readiness
67function checkParkReadiness() {
68 console.log("Starting park readiness check for opening...");
69
70 const startTime = Date.now();
71
72 // Launch all checks in parallel
73 const security = checkSecuritySystems();
74 const dinosaurs = checkDinosaurHealthStatus();
75 const power = checkPowerSystems();
76 const weather = checkWeather();
77
78 // Use Promise.all to wait for all results
79 return Promise.all([security, dinosaurs, power, weather])
80 .then(results => {
81 const endTime = Date.now();
82 const czasWykonania = (endTime - startTime) / 1000;
83
84 // Unpack results
85 const [securityResult, dinosaursResult, powerResult, weatherResult] = results;
86
87 return {
88 status: "PARK READY TO OPEN",
89 security: securityResult,
90 dinosaurs: dinosaursResult,
91 power: powerResult,
92 weather: weatherResult,
93 checkDuration: `${czasWykonania} sekund`
94 };
95 })
96 .catch(error => {
97 throw new Error(`Park is not ready to open: ${error.message}`);
98 });
99}
100
101// Using our function
102checkParkReadiness()
103 .then(report => {
104 console.log("PARK READINESS REPORT:");
105 console.log(JSON.stringify(report, null, 2));
106 })
107 .catch(error => {
108 console.error("ERROR:", error.message);
109 });In the example above:
Promise.all() to wait for all checks to completeNotice that all operations are launched simultaneously (not one after another), which significantly speeds up the whole process. Instead of waiting 2000 + 3000 + 1500 + 1000 = 7500 ms, we only wait for the longest operation, approximately 3000 ms.
The result of a fulfilled promise returned by
Promise.all() is an array of results in the same order as the input array of promises. We can use array destructuring to elegantly assign names to individual results:1Promise.all([pobierzDino(1), pobierzDino(2), pobierzDino(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 });Remember that
Promise.all() uses an "all or nothing" approach. If any promise is rejected, the entire operation fails. This is not always the desired behavior.1const sectors = ["A", "B", "C", "D", "E"];
2
3// Map sectors to promises, then wait for all
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(`All ${results.length} fence sectors operational`);
8 console.log(`Average fence voltage: ${(totalVoltage / results.length).toFixed(0)}V`);
9 })
10 .catch(error => {
11 console.error("Fence system failure:", error.message);
12 });Promise.allSettled() takes an iterable of promises and returns a Promise that always resolves (never rejects) once all promises have settled. Each result is an object with:{ status: "fulfilled", value: ... } for resolved promises{ status: "rejected", reason: ... } for rejected promises1const monitoringTasks = [
2 checkFenceSystem("A"),
3 checkFenceSystem("B"),
4 checkCameraSystem("East"),
5 checkMotionSensors("Raptor-Pen"),
6 checkMotionSensors("Herbivore-Valley")
7];
8
9// We want a full status report even if some systems fail
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(`=== Park Status Report ===`);
16 console.log(`Systems OK: ${successful.length} / ${results.length}`);
17 console.log(`Systems FAILED: ${failed.length} / ${results.length}`);
18
19 if (failed.length > 0) {
20 console.log("\nFailures:");
21 failed.forEach(r => console.log(` - ${r.reason.message}`));
22 }
23
24 // Decide whether to open based on failure count
25 if (failed.length === 0) {
26 console.log("\nDecision: OPEN — all systems nominal");
27 } else if (failed.length <= 1) {
28 console.log("\nDecision: OPEN WITH CAUTION — minor issues detected");
29 } else {
30 console.log("\nDecision: CLOSED — too many system failures");
31 }
32 });Let's compare the behavior of
Promise.allSettled() with Promise.all():1// An array containing promises, some of which are rejected
2const mixedPromises = [
3 Promise.resolve("Success 1"),
4 Promise.reject(new Error("Error 1")),
5 Promise.resolve("Success 2"),
6 Promise.reject(new Error("Error 2"))
7];
8
9// Using Promise.all
10Promise.all(mixedPromises)
11 .then(results => {
12 console.log("All succeeded:", results);
13 // This code will never execute because of rejected Promises in the array
14 })
15 .catch(error => {
16 console.error("Promise.all caught error:", error.message);
17 // Displays: "Promise.all caught error: Error 1"
18 // Note that we only get the first error, not all of them
19 });
20
21// Using Promise.allSettled
22Promise.allSettled(mixedPromises)
23 .then(results => {
24 console.log("Results of all Promises:");
25
26 // Process all results
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}: ERROR - ${result.reason.message}`);
32 }
33 });
34
35 // Displays:
36 // "Promise 0: OK - Success 1"
37 // "Promise 1: ERROR - Error 1"
38 // "Promise 2: OK - Success 2"
39 // "Promise 3: ERROR - Error 2"
40 });The key difference:
Promise.all() fails on the first error, while Promise.allSettled() always succeeds with information about all results.Promise.race() returns a Promise that settles as soon as one input promise settles — either resolves or rejects. The "winner" determines the outcome.This is an ideal solution for scenarios where:
Imagine we have several different dinosaur species recognition systems (visual, DNA, behavioral) and we want to use the result from whichever one works fastest:
1function recognizeVisually(dinosaurImage) {
2 console.log("Starting visual analysis...");
3 return new Promise((resolve, reject) => {
4 setTimeout(() => {
5 if (Math.random() > 0.3) { // 70% chance of success
6 resolve({
7 method: "visual",
8 species: "Velociraptor",
9 confidence: 0.82,
10 timeMs: 1800
11 });
12 } else {
13 reject(new Error("Visual recognition failed"));
14 }
15 }, 1800);
16 });
17}
18
19function recognizeByDNA(dnaSample) {
20 console.log("Starting DNA analysis...");
21 return new Promise((resolve, reject) => {
22 setTimeout(() => {
23 if (Math.random() > 0.1) { // 90% chance of success
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("DNA sample contaminated"));
33 }
34 }, 3500);
35 });
36}
37
38function recognizeBehavior(behavioralRecording) {
39 console.log("Starting behavioral analysis...");
40 return new Promise((resolve, reject) => {
41 setTimeout(() => {
42 if (Math.random() > 0.4) { // 60% chance of success
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("Insufficient behavioral data"));
52 }
53 }, 2200);
54 });
55}
56
57// Quick dinosaur recognition function
58function quickRecognition(data) {
59 console.log("Starting quick dinosaur recognition...");
60
61 return Promise.race([
62 recognizeVisually(data.image),
63 recognizeByDNA(data.dna),
64 recognizeBehavior(data.recording)
65 ]);
66}
67
68// Usage
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(`Dinosaur recognized as: ${result.species}`);
78 console.log(`Method: ${result.method}, Confidence: ${result.confidence * 100}%`);
79 console.log(`Czas rozpoznania: ${result.timeMs} ms`);
80 })
81 .catch(error => {
82 console.error("Failed to recognize dinosaur:", error.message);
83 });In this example we launch three recognition methods in parallel and use the first available result. The visual method is the fastest (1800 ms) but has the lowest accuracy. The DNA method is the slowest (3500 ms) but the most accurate. The behavioral method is in between. Thanks to
Promise.race() we get the first available result, allowing a quick response.We often need to abort long-running operations if they take too long.
Promise.race() is the ideal tool for implementing a timeout:1// Classic use case: timeout pattern
2function withTimeout(promise, timeoutMs, label) {
3 const timeout = new Promise((_, reject) =>
4 setTimeout(() => reject(new Error(`${label} timed out after ${timeoutMs}ms`)), timeoutMs)
5 );
6 return Promise.race([promise, timeout]);
7}
8
9// Slow sensor with a timeout guard
10function readSlowSensor(sensorId) {
11 return new Promise(resolve => {
12 const delay = 1000 + Math.random() * 4000; // 1-5 seconds
13 setTimeout(() => resolve({ sensorId, reading: Math.random() * 100 }), delay);
14 });
15}
16
17withTimeout(readSlowSensor("SENSOR-42"), 2000, "Sensor 42 read")
18 .then(data => {
19 console.log(`Sensor reading: ${data.reading.toFixed(2)}`);
20 })
21 .catch(error => {
22 console.error(error.message);
23 // Use cached/default value instead
24 console.log("Using last known sensor value: 67.3");
25 });1// Try multiple API mirrors — use whichever responds first
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; // Variable latency per region
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(`Fastest mirror: ${result.url} (${result.latency.toFixed(0)}ms)`);
18 });Promise.any() is similar to race() but only resolves on the first fulfilled promise (ignores rejections unless all reject, in which case it rejects with an AggregateError).1// Try multiple backup sensors — succeed as soon as any one responds
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(`Sensor ${id} offline`));
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(`Got reading from sensor ${result.sensorId}: ${result.value.toFixed(1)}°C`);
21 })
22 .catch(error => {
23 // AggregateError — all sensors failed
24 console.error("All temperature sensors offline:", error.message);
25 console.log("Triggering emergency temperature protocol...");
26 });1function testEmergencyGate(id) {
2 console.log(`Testing emergency gate #${id}...`);
3 return new Promise((resolve, reject) => {
4 setTimeout(() => {
5 const works = Math.random() > 0.6; // 40% chance of failure
6 if (works) {
7 resolve({
8 id: id,
9 status: "Operational",
10 openingTime: `${(Math.random() * 2 + 1).toFixed(1)}s`
11 });
12 } else {
13 reject(new Error(`Gate #${id} is not responding to the opening signal`));
14 }
15 }, 1000 + Math.random() * 2000);
16 });
17}
18
19// Attempt to open any working emergency gate
20function findWorkingGate() {
21 console.log("Attempting to open any working emergency gate...");
22
23 // Test all gates simultaneously
24 return Promise.any([
25 testEmergencyGate(1),
26 testEmergencyGate(2),
27 testEmergencyGate(3),
28 testEmergencyGate(4)
29 ]);
30}
31
32// Usage
33findWorkingGate()
34 .then(workingGate => {
35 console.log(`Found a working emergency gate: #${workingGate.id}`);
36 console.log(`Opening time: ${workingGate.openingTime}`);
37 console.log("Starting evacuation through this gate...");
38 })
39 .catch(error => {
40 // This catch executes only when ALL gates fail
41 console.error("CRITICAL ERROR: All emergency gates are not working!");
42 console.error("Details:", error.errors.map(e => e.message).join(", "));
43 console.log("Activating emergency gate breach procedure...");
44 });In this example
Promise.any() lets us find the first working emergency gate and immediately start evacuation through it, ignoring gates that don't work. Only when all gates fail does the whole operation fail.1// ✅ Promise.all() — ALL must succeed; fail fast if any fail
2// Use when: fetching resources that are ALL required for the next step
3const [dinoData, enclosureData, staffData] = await Promise.all([
4 fetchDinosaur(id),
5 fetchEnclosure(id),
6 fetchStaff(id)
7]);
8
9// ✅ Promise.allSettled() — collect ALL results; never throws
10// Use when: generating a status report, running independent background tasks
11const systemStatuses = await Promise.allSettled([
12 checkFences(), checkCameras(), checkSensors(), checkPower()
13]);
14
15// ✅ Promise.race() — first to SETTLE (resolve OR reject) wins
16// Use when: implementing timeouts, racing with a deadline
17const result = await Promise.race([fetchData(), timeout(3000)]);
18
19// ✅ Promise.any() — first to RESOLVE wins (ignores rejections)
20// Use when: trying multiple sources and any success is acceptable
21const reading = await Promise.any([sensor1(), sensor2(), sensor3()]);1async function startParkOperations() {
2 console.log("=== Jurassic Park — Daily Startup Sequence ===\n");
3
4 // Phase 1: Critical systems — ALL must be online
5 console.log("Phase 1: Checking critical systems...");
6 try {
7 const [fences, power, locks] = await Promise.all([
8 checkFenceSystem("perimeter"),
9 checkPowerGrid(),
10 checkGateLocks()
11 ]);
12 console.log("✅ All critical systems nominal");
13 } catch (error) {
14 console.error("❌ Critical system failure:", error.message);
15 console.log("ABORT: Cannot open park — critical system offline");
16 return;
17 }
18
19 // Phase 2: Secondary systems — report status but do not block opening
20 console.log("\nPhase 2: Checking secondary systems...");
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} secondary systems need attention`);
31 failedSecondary.forEach(r => console.warn(` - ${r.reason.message}`));
32 } else {
33 console.log("✅ All secondary systems ready");
34 }
35
36 // Phase 3: Race to get park readiness from fastest data source
37 console.log("\nPhase 3: Fetching visitor data...");
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} visitors expected today`);
45 } catch (error) {
46 console.warn("⚠️ Could not fetch visitor data — proceeding without it");
47 }
48
49 console.log("\n🦕 Park is open — welcome to Jurassic Park!");
50}
51
52// Stubs
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("Restaurant B kitchen equipment offline")), 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 after ${ms}ms`)), ms)); }
60
61startParkOperations();Often we need to dynamically create an array of promises based on data:
1// Fetching data on all dinosaurs in the park
2function fetchAllDinosaurData(ids) {
3 console.log(`Fetching data for ${ids.length} dinosaurs...`);
4
5 // Map IDs to promises
6 const promises = ids.map(id => fetchDinosaurInfo(id));
7
8 // Use Promise.all for parallel fetching
9 return Promise.all(promises);
10}
11
12// Usage
13const dinosaurIds = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
14
15fetchAllDinosaurData(dinosaurIds)
16 .then(dinosaurs => {
17 console.log(`Fetched data for ${dinosaurs.length} dinosaurs`);
18
19 // Calculate statistics
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(`Statistics: ${carnivores} carnivores, ${herbivores} herbivores, ${omnivores} omnivores`);
25 })
26 .catch(error => {
27 console.error("Error fetching data:", error.message);
28 });We can combine different Promise composition methods to achieve more complex behaviors:
1function monitorDinosaurs() {
2 // Fetch all park sectors
3 return fetchAllSectors()
4 .then(sectors => {
5 // For each sector, check dinosaurs in parallel
6 const sectorPromises = sectors.map(sector => {
7 // For each sector use allSettled to have info about all dinosaurs
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 // Use allSettled for dinosaurs within a sector
18 return Promise.allSettled(dinosaurPromises)
19 .then(results => ({
20 sector: sector.id,
21 nazwa: sector.name,
22 results: results
23 }));
24 });
25
26 // Use all for sectors because we need data from all of them
27 return Promise.all(sectorPromises);
28 });
29}When we have many asynchronous operations to perform, it is not always a good idea to launch them all simultaneously — it can overload the system. We can implement a mechanism to limit the number of parallel operations:
1// Function that executes tasks with limited parallelism
2async function executeTasksWithLimit(tasks, maxParallel) {
3 const results = [];
4
5 // Process tasks in batches
6 for (let i = 0; i < tasks.length; i += maxParallel) {
7 const batch = tasks.slice(i, i + maxParallel);
8
9 // Execute batch of tasks in parallel
10 const batchResult = await Promise.allSettled(batch.map(zadanie => zadanie()));
11 results.push(...batchResult);
12
13 console.log(`Completed batch ${Math.floor(i / maxParallel) + 1}/${Math.ceil(tasks.length / maxParallel)}`);
14 }
15
16 return results;
17}
18
19// Example usage
20const dinosaurTasks = Array.from({ length: 50 }, (_, i) => {
21 return () => checkDinosaurStatus(i + 1);
22});
23
24// Execute at most 5 tasks simultaneously
25executeTasksWithLimit(dinosaurTasks, 5)
26 .then(results => {
27 console.log(`Completed checking all ${results.length} dinosaurs`);
28 // Analyze results...
29 });The static methods of Promise (
Promise.all(), Promise.race(), Promise.allSettled(), and Promise.any()) provide powerful tools for composing and coordinating multiple asynchronous operations. Choosing the right method depends on the specific requirements of your application:| Method | Resolves when | Rejects when | Use case | |--------|---------------|--------------|----------| |
Promise.all() | ALL resolve | ANY rejects | All required |
| Promise.allSettled() | ALL settle | Never | Status reports |
| Promise.race() | FIRST settles | FIRST rejects | Timeout pattern |
| Promise.any() | FIRST resolves | ALL reject | Fallback sources |In Jurassic Park, as in any complex JavaScript application, the ability to effectively compose asynchronous operations is crucial for creating responsive, efficient, and reliable systems.
In the next exercise we will learn the
async/await syntax, which provides an even more readable way of working with asynchronous code.