Congratulations — you have reached the final project of this module! Throughout the module you have been learning about asynchronicity in JavaScript — from callbacks, through Promises, all the way to elegant
async/await. Now it is time to combine all those skills into one cohesive system.Imagine that the Jurassic Park management has commissioned you to build a central monitoring system that collects data from all park sectors in real time, analyzes threats, and coordinates responses to incidents. The system must handle many operations simultaneously — from motion sensor readings in raptor pens, through power system monitoring, to communication with field teams. Each of these operations takes a different amount of time and can fail — and in a park full of dinosaurs there is no room for unhandled errors!
In this project you will build a complete monitoring system using callbacks, Promises, async/await, error handling, and parallel execution of asynchronous operations.
You will create an asynchronous Jurassic Park monitoring system consisting of four subsystems:
The first subsystem is the low-level layer that simulates readings from sensors distributed throughout the park. We use callbacks because they represent the oldest async pattern — similar to the old park systems running on legacy technology.
1// Sensor reading simulation — using callbacks
2function readSensor(sensorId, sectorName, callback) {
3 const delay = Math.random() * 2000 + 500; // 0.5-2.5 seconds
4
5 setTimeout(() => {
6 // 15% chance of sensor failure
7 if (Math.random() < 0.15) {
8 callback(new Error(`Sensor ${sensorId} in sector ${sectorName} not responding`), null);
9 return;
10 }
11
12 const sensorData = {
13 sensorId,
14 sector: sectorName,
15 timestamp: new Date().toISOString(),
16 temperature: Math.round(20 + Math.random() * 25),
17 motion: Math.random() > 0.5,
18 fenceIntegrity: Math.round(70 + Math.random() * 30),
19 powerLevel: Math.round(50 + Math.random() * 50)
20 };
21
22 callback(null, sensorData);
23 }, delay);
24}
25
26// Reading multiple sensors from one sector — callback pattern
27function scanSector(sectorName, sensorCount, callback) {
28 const results = [];
29 const errors = [];
30 let completed = 0;
31
32 for (let i = 1; i <= sensorCount; i++) {
33 readSensor(`SENSOR-${sectorName}-${i}`, sectorName, (error, data) => {
34 completed++;
35
36 if (error) {
37 errors.push(error.message);
38 } else {
39 results.push(data);
40 }
41
42 // When all sensors have responded
43 if (completed === sensorCount) {
44 callback({
45 sector: sectorName,
46 successfulReads: results,
47 failedReads: errors,
48 scanCompletedAt: new Date().toISOString()
49 });
50 }
51 });
52 }
53}Note the "callback counter" pattern — we count completed operations and only call the final callback when all have finished.
The second subsystem handles communication between park sectors. Instead of callbacks we use Promises, which allows more readable operation chaining and better error handling.
1// Convert callback to Promise (promisification)
2function readSensorAsync(sensorId, sectorName) {
3 return new Promise((resolve, reject) => {
4 readSensor(sensorId, sectorName, (error, data) => {
5 if (error) reject(error);
6 else resolve(data);
7 });
8 });
9}
10
11// Send alert to command center
12function sendAlert(alertData) {
13 return new Promise((resolve, reject) => {
14 const delay = Math.random() * 1000 + 200;
15
16 setTimeout(() => {
17 if (Math.random() < 0.1) {
18 reject(new Error(`Failed to send alert: communication system overloaded`));
19 return;
20 }
21
22 resolve({
23 alertId: `ALERT-${Date.now()}`,
24 status: "sent",
25 data: alertData,
26 confirmedAt: new Date().toISOString()
27 });
28 }, delay);
29 });
30}
31
32// Promise chain — read, analyze, alert
33function monitorAndAlert(sensorId, sectorName) {
34 return readSensorAsync(sensorId, sectorName)
35 .then(sensorData => {
36 console.log(`Reading from ${sensorId}: temp=${sensorData.temperature}°C, fence=${sensorData.fenceIntegrity}%`);
37
38 // Data analysis — is there a threat?
39 if (sensorData.fenceIntegrity < 80 || sensorData.motion) {
40 return {
41 level: sensorData.fenceIntegrity < 75 ? "CRITICAL" : "WARNING",
42 sensor: sensorId,
43 sector: sectorName,
44 details: sensorData
45 };
46 }
47 return null; // No threat
48 })
49 .then(threat => {
50 if (threat) {
51 return sendAlert(threat);
52 }
53 return { status: "no threats", sensor: sensorId };
54 })
55 .catch(error => {
56 console.error(`Monitoring error ${sensorId}: ${error.message}`);
57 return { status: "error", sensor: sensorId, error: error.message };
58 })
59 .finally(() => {
60 console.log(`Monitoring cycle complete for ${sensorId}`);
61 });
62}Notice how
.then(), .catch(), and .finally() create a readable data flow with full error handling.The third subsystem is the analytics layer that processes sensor data and classifies threats. We use
async/await for maximum code readability.1// Threat level classification
2async function classifyThreat(sectorData) {
3 // Simulate AI processing time
4 await new Promise(resolve => setTimeout(resolve, 300));
5
6 const { successfulReads, failedReads } = sectorData;
7
8 const avgFenceIntegrity = successfulReads.reduce(
9 (sum, s) => sum + s.fenceIntegrity, 0
10 ) / successfulReads.length;
11
12 const motionDetected = successfulReads.filter(s => s.motion).length;
13 const sensorFailures = failedReads.length;
14
15 let threatLevel = "GREEN"; // Safe
16 const warnings = [];
17
18 if (avgFenceIntegrity < 75) {
19 threatLevel = "RED";
20 warnings.push(`Critical fence condition: ${avgFenceIntegrity.toFixed(1)}%`);
21 } else if (avgFenceIntegrity < 85) {
22 threatLevel = "YELLOW";
23 warnings.push(`Degraded fence integrity: ${avgFenceIntegrity.toFixed(1)}%`);
24 }
25
26 if (motionDetected > successfulReads.length * 0.6) {
27 threatLevel = "RED";
28 warnings.push(`Intense motion detected in ${motionDetected} sensors`);
29 }
30
31 if (sensorFailures > 2) {
32 warnings.push(`${sensorFailures} sensors not responding — possible sabotage!`);
33 if (threatLevel !== "RED") threatLevel = "YELLOW";
34 }
35
36 return {
37 sector: sectorData.sector,
38 threatLevel,
39 warnings,
40 avgFenceIntegrity: avgFenceIntegrity.toFixed(1),
41 activeSensors: successfulReads.length,
42 failedSensors: sensorFailures,
43 motionDetections: motionDetected,
44 analyzedAt: new Date().toISOString()
45 };
46}
47
48// Main sector analysis function with error handling
49async function analyzeSector(sectorName, sensorCount) {
50 console.log(`\n🔍 Starting sector analysis: ${sectorName}`);
51
52 try {
53 // Scan sector (convert callback to Promise)
54 const scanResult = await new Promise((resolve) => {
55 scanSector(sectorName, sensorCount, resolve);
56 });
57
58 console.log(`Scan complete: ${scanResult.successfulReads.length} readings, ${scanResult.failedReads.length} errors`);
59
60 // Classify threat
61 const analysis = await classifyThreat(scanResult);
62
63 // If threat detected, send alert
64 if (analysis.threatLevel !== "GREEN") {
65 try {
66 const alertResult = await sendAlert({
67 type: "SECTOR_ANALYSIS",
68 ...analysis
69 });
70 console.log(`Alert sent: ${alertResult.alertId}`);
71 analysis.alertSent = true;
72 analysis.alertId = alertResult.alertId;
73 } catch (alertError) {
74 console.error(`Failed to send alert: ${alertError.message}`);
75 analysis.alertSent = false;
76 analysis.alertError = alertError.message;
77 }
78 }
79
80 return analysis;
81
82 } catch (error) {
83 console.error(`Critical analysis error for sector ${sectorName}: ${error.message}`);
84 return {
85 sector: sectorName,
86 threatLevel: "UNKNOWN",
87 error: error.message,
88 analyzedAt: new Date().toISOString()
89 };
90 }
91}Note the nested
try/catch — the outer block handles critical analysis errors, while the inner one allows the analysis to continue even if sending an alert fails.The final subsystem is the heart of the monitoring system. It coordinates parallel scanning of all park sectors and makes decisions based on aggregated data.
1// Park sector configuration
2const parkSectors = [
3 { name: "T-Rex-Paddock", sensors: 5 },
4 { name: "Raptor-Pen", sensors: 4 },
5 { name: "Herbivore-Valley", sensors: 6 },
6 { name: "Aviary", sensors: 3 },
7 { name: "Marine-Exhibit", sensors: 4 }
8];
9
10// Full park scan — all sectors in parallel
11async function fullParkScan() {
12 console.log("=== STARTING FULL PARK SCAN ===\n");
13 const startTime = Date.now();
14
15 // Promise.allSettled — continue even if one sector fails
16 const sectorPromises = parkSectors.map(
17 sector => analyzeSector(sector.name, sector.sensors)
18 );
19
20 const results = await Promise.allSettled(sectorPromises);
21
22 const report = {
23 scanDuration: `${((Date.now() - startTime) / 1000).toFixed(1)}s`,
24 sectors: [],
25 overallStatus: "GREEN"
26 };
27
28 results.forEach((result, index) => {
29 if (result.status === "fulfilled") {
30 report.sectors.push(result.value);
31 if (result.value.threatLevel === "RED") {
32 report.overallStatus = "RED";
33 } else if (result.value.threatLevel === "YELLOW" && report.overallStatus !== "RED") {
34 report.overallStatus = "YELLOW";
35 }
36 } else {
37 report.sectors.push({
38 sector: parkSectors[index].name,
39 threatLevel: "ERROR",
40 error: result.reason?.message || "Unknown error"
41 });
42 report.overallStatus = "RED";
43 }
44 });
45
46 return report;
47}
48
49// Timed scan — Promise.race
50async function timedScan(sectorName, sensorCount, timeoutMs) {
51 const scanPromise = analyzeSector(sectorName, sensorCount);
52
53 const timeoutPromise = new Promise((_, reject) => {
54 setTimeout(() => {
55 reject(new Error(`Timeout: scan of sector ${sectorName} exceeded ${timeoutMs}ms`));
56 }, timeoutMs);
57 });
58
59 try {
60 return await Promise.race([scanPromise, timeoutPromise]);
61 } catch (error) {
62 return {
63 sector: sectorName,
64 threatLevel: "TIMEOUT",
65 error: error.message,
66 analyzedAt: new Date().toISOString()
67 };
68 }
69}
70
71// Run full scan and display report
72async function runParkMonitoring() {
73 try {
74 const report = await fullParkScan();
75
76 console.log("\n=== FINAL REPORT ===");
77 console.log(`Scan duration: ${report.scanDuration}`);
78 console.log(`Overall status: ${report.overallStatus}`);
79 console.log("\nSectors:");
80
81 report.sectors.forEach(sector => {
82 const icon = sector.threatLevel === "GREEN" ? "✅" :
83 sector.threatLevel === "YELLOW" ? "⚠️" :
84 sector.threatLevel === "RED" ? "🔴" : "❓";
85 console.log(` ${icon} ${sector.sector}: ${sector.threatLevel}`);
86
87 if (sector.warnings?.length > 0) {
88 sector.warnings.forEach(w => console.log(` → ${w}`));
89 }
90 });
91
92 if (report.overallStatus === "RED") {
93 console.log("\n🚨 ALERT: Critical threats detected! Activating emergency protocol.");
94 }
95
96 return report;
97 } catch (error) {
98 console.error("Critical monitoring system error:", error.message);
99 }
100}
101
102// Start monitoring system
103runParkMonitoring();When building your own monitoring system, remember a few key principles:
File structure:
1// sensors.js - sensor module (callbacks)
2// communication.js - communication module (Promises)
3// analysis.js - analysis module (async/await)
4// coordinator.js - central coordinator (Promise.all/race/allSettled)
5// main.js - entry pointError handling at every level:
1// Level 1: Callback — error-first pattern
2function callbackOperation(callback) {
3 // callback(error, data) — always check error
4}
5
6// Level 2: Promise — .catch() at end of chain
7promiseOperation()
8 .then(result => process(result))
9 .catch(error => handleError(error));
10
11// Level 3: async/await — try/catch with finally
12async function asyncOperation() {
13 try {
14 const result = await riskyOperation();
15 return result;
16 } catch (error) {
17 logError(error);
18 return fallbackValue;
19 } finally {
20 cleanup();
21 }
22}Choosing the right Promise method for different scenarios:
1// All must succeed → Promise.all()
2const allResults = await Promise.all([task1(), task2()]);
3
4// Continue despite errors → Promise.allSettled()
5const mixedResults = await Promise.allSettled([task1(), task2()]);
6
7// Fastest wins → Promise.race()
8const fastest = await Promise.race([task1(), timeout(5000)]);
9
10// First success → Promise.any()
11const firstSuccess = await Promise.any([server1(), server2()]);This project is your chance to show that you have mastered asynchronicity in JavaScript. Just like in a real Jurassic Park, your system must be resilient to failures, fast to respond, and capable of handling many operations simultaneously. Remember the words of Dr. Ian Malcolm: "Life finds a way" — and your code must be prepared for that!
Build your monitoring system in the editor below. Implement at least three of the four subsystems and make sure the system properly handles errors and displays a readable final report.