Gratulacje, dotarłeś do końcowego projektu tego modułu! Przez cały czas uczyłeś się o asynchroniczności w JavaScript - od callbacków, przez Promise, aż po eleganckie
async/await. Teraz nadszedł czas, aby połączyć wszystkie te umiejętności w jeden, spójny system.Wyobraź sobie, że zarząd Parku Jurajskiego zlecił Ci stworzenie centralnego systemu monitoringu, który w czasie rzeczywistym zbiera dane ze wszystkich sektorów parku, analizuje zagrożenia i koordynuje reakcje na incydenty. System musi obsługiwać wiele operacji jednocześnie - od odczytów z czujników ruchu w wybiegach raptorów, przez monitoring systemów zasilania, po komunikację z zespołami terenowymi. Każda z tych operacji trwa różną ilość czasu i może zakończyć się niepowodzeniem - a w parku pełnym dinozaurów nie ma miejsca na nieobsłużone błędy!
W tym projekcie zbudujesz kompletny system monitoringu, wykorzystując callbacki, Promise, async/await, obsługę błędów oraz równoległe wykonywanie operacji asynchronicznych.
Stworzysz asynchroniczny system monitoringu Parku Jurajskiego składający się z czterech podsystemów:
Pierwszy podsystem to warstwa niskopoziomowa, która symuluje odczyty z czujników rozmieszczonych w całym parku. Używamy callbacków, ponieważ reprezentują one najstarszy wzorzec asynchroniczny - podobnie jak stare systemy w parku, które działają na starszej technologii.
1// Symulacja odczytu z czujnika - używamy callbacków
2function readSensor(sensorId, sectorName, callback) {
3 const delay = Math.random() * 2000 + 500; // 0.5-2.5 sekundy
4
5 setTimeout(() => {
6 // 15% szans na awarię czujnika
7 if (Math.random() < 0.15) {
8 callback(new Error(`Czujnik ${sensorId} w sektorze ${sectorName} nie odpowiada`), 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// Odczyt wielu czujników z jednego sektora - callback hell
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 // Gdy wszystkie czujniki odpowiedziały
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}Zwróć uwagę na wzorzec "callback counter" - zliczamy zakończone operacje i wywołujemy finalny callback dopiero gdy wszystkie się zakończą.
Drugi podsystem odpowiada za komunikację między sektorami parku. Zamiast callbacków używamy Promise, co pozwala na czytelniejsze łańcuchowanie operacji i lepszą obsługę błędów.
1// Konwersja callbacka na Promise (promisyfikacja)
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// Wysyłanie alertu do centrum dowodzenia
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(`Nie udało się wysłać alertu: system komunikacji przeciążony`));
19 return;
20 }
21
22 resolve({
23 alertId: `ALERT-${Date.now()}`,
24 status: "wysłany",
25 data: alertData,
26 confirmedAt: new Date().toISOString()
27 });
28 }, delay);
29 });
30}
31
32// Łańcuch Promise - odczyt, analiza, alert
33function monitorAndAlert(sensorId, sectorName) {
34 return readSensorAsync(sensorId, sectorName)
35 .then(sensorData => {
36 console.log(`Odczyt z ${sensorId}: temp=${sensorData.temperature}°C, ogrodzenie=${sensorData.fenceIntegrity}%`);
37
38 // Analiza danych - czy jest zagrożenie?
39 if (sensorData.fenceIntegrity < 80 || sensorData.motion) {
40 return {
41 level: sensorData.fenceIntegrity < 75 ? "KRYTYCZNY" : "OSTRZEŻENIE",
42 sensor: sensorId,
43 sector: sectorName,
44 details: sensorData
45 };
46 }
47 return null; // Brak zagrożenia
48 })
49 .then(threat => {
50 if (threat) {
51 return sendAlert(threat);
52 }
53 return { status: "brak zagrożeń", sensor: sensorId };
54 })
55 .catch(error => {
56 console.error(`Błąd monitoringu ${sensorId}: ${error.message}`);
57 return { status: "błąd", sensor: sensorId, error: error.message };
58 })
59 .finally(() => {
60 console.log(`Zakończono cykl monitoringu dla ${sensorId}`);
61 });
62}Zauważ, jak
.then(), .catch() i .finally() tworzą czytelny przepływ danych z pełną obsługą błędów.Trzeci podsystem to warstwa analityczna, która przetwarza dane z czujników i klasyfikuje zagrożenia. Używamy
async/await dla maksymalnej czytelności kodu.1// Klasyfikacja poziomu zagrożenia
2async function classifyThreat(sectorData) {
3 // Symulacja czasu przetwarzania AI
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 = "ZIELONY"; // Bezpiecznie
16 const warnings = [];
17
18 if (avgFenceIntegrity < 75) {
19 threatLevel = "CZERWONY";
20 warnings.push(`Krytyczny stan ogrodzeń: ${avgFenceIntegrity.toFixed(1)}%`);
21 } else if (avgFenceIntegrity < 85) {
22 threatLevel = "ŻÓŁTY";
23 warnings.push(`Obniżona integralność ogrodzeń: ${avgFenceIntegrity.toFixed(1)}%`);
24 }
25
26 if (motionDetected > successfulReads.length * 0.6) {
27 threatLevel = "CZERWONY";
28 warnings.push(`Intensywny ruch wykryty w ${motionDetected} czujnikach`);
29 }
30
31 if (sensorFailures > 2) {
32 warnings.push(`${sensorFailures} czujników nie odpowiada - możliwa sabotaż!`);
33 if (threatLevel !== "CZERWONY") threatLevel = "ŻÓŁTY";
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// Główna funkcja analizy sektora z obsługą błędów
49async function analyzeSector(sectorName, sensorCount) {
50 console.log(`\n🔍 Rozpoczynam analizę sektora: ${sectorName}`);
51
52 try {
53 // Skanowanie sektora (konwersja callbacka na Promise)
54 const scanResult = await new Promise((resolve) => {
55 scanSector(sectorName, sensorCount, resolve);
56 });
57
58 console.log(`Skan zakończony: ${scanResult.successfulReads.length} odczytów, ${scanResult.failedReads.length} błędów`);
59
60 // Klasyfikacja zagrożenia
61 const analysis = await classifyThreat(scanResult);
62
63 // Jeśli zagrożenie, wyślij alert
64 if (analysis.threatLevel !== "ZIELONY") {
65 try {
66 const alertResult = await sendAlert({
67 type: "ANALIZA_SEKTORA",
68 ...analysis
69 });
70 console.log(`Alert wysłany: ${alertResult.alertId}`);
71 analysis.alertSent = true;
72 analysis.alertId = alertResult.alertId;
73 } catch (alertError) {
74 console.error(`Nie udało się wysłać alertu: ${alertError.message}`);
75 analysis.alertSent = false;
76 analysis.alertError = alertError.message;
77 }
78 }
79
80 return analysis;
81
82 } catch (error) {
83 console.error(`Krytyczny błąd analizy sektora ${sectorName}: ${error.message}`);
84 return {
85 sector: sectorName,
86 threatLevel: "NIEZNANY",
87 error: error.message,
88 analyzedAt: new Date().toISOString()
89 };
90 }
91}Zauważ zagnieżdżony
try/catch - zewnętrzny blok obsługuje krytyczne błędy analizy, a wewnętrzny pozwala na kontynuację nawet gdy wysyłanie alertu się nie powiedzie.Ostatni podsystem to serce systemu monitoringu. Koordynuje równoległe skanowanie wszystkich sektorów parku i podejmuje decyzje na podstawie zagregowanych danych.
1// Konfiguracja sektorów parku
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// Pełne skanowanie parku - wszystkie sektory równolegle
11async function fullParkScan() {
12 console.log("=== ROZPOCZYNAM PEŁNE SKANOWANIE PARKU ===\n");
13 const startTime = Date.now();
14
15 // Promise.allSettled - kontynuuj nawet jeśli jeden sektor zawiedzie
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: "ZIELONY"
26 };
27
28 results.forEach((result, index) => {
29 if (result.status === "fulfilled") {
30 report.sectors.push(result.value);
31 if (result.value.threatLevel === "CZERWONY") {
32 report.overallStatus = "CZERWONY";
33 } else if (result.value.threatLevel === "ŻÓŁTY" && report.overallStatus !== "CZERWONY") {
34 report.overallStatus = "ŻÓŁTY";
35 }
36 } else {
37 report.sectors.push({
38 sector: parkSectors[index].name,
39 threatLevel: "BŁĄD",
40 error: result.reason?.message || "Nieznany błąd"
41 });
42 report.overallStatus = "CZERWONY";
43 }
44 });
45
46 return report;
47}
48
49// Skanowanie z limitem czasu - 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: skan sektora ${sectorName} przekroczył ${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// Uruchomienie pełnego skanowania i wyświetlenie raportu
72async function runParkMonitoring() {
73 try {
74 const report = await fullParkScan();
75
76 console.log("\n=== RAPORT KOŃCOWY ===");
77 console.log(`Czas skanowania: ${report.scanDuration}`);
78 console.log(`Status ogólny: ${report.overallStatus}`);
79 console.log("\nSektory:");
80
81 report.sectors.forEach(sector => {
82 const icon = sector.threatLevel === "ZIELONY" ? "✅" :
83 sector.threatLevel === "ŻÓŁTY" ? "⚠️" :
84 sector.threatLevel === "CZERWONY" ? "🔴" : "❓";
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 === "CZERWONY") {
93 console.log("\n🚨 UWAGA: Wykryto krytyczne zagrożenia! Uruchomienie protokołu awaryjnego.");
94 }
95
96 return report;
97 } catch (error) {
98 console.error("Krytyczny błąd systemu monitoringu:", error.message);
99 }
100}
101
102// Start systemu
103runParkMonitoring();Przy tworzeniu własnego systemu monitoringu pamiętaj o kilku kluczowych zasadach:
Struktura plików:
1// sensors.js - moduł czujników (callbacki)
2// communication.js - moduł komunikacji (Promise)
3// analysis.js - moduł analizy (async/await)
4// coordinator.js - centralny koordynator (Promise.all/race/allSettled)
5// main.js - punkt startowyObsługa błędów na każdym poziomie:
1// Poziom 1: Callback - error-first pattern
2function callbackOperation(callback) {
3 // callback(error, data) - zawsze sprawdzaj error
4}
5
6// Poziom 2: Promise - .catch() na końcu łańcucha
7promiseOperation()
8 .then(result => process(result))
9 .catch(error => handleError(error));
10
11// Poziom 3: async/await - try/catch z 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}Wybór metody Promise dla różnych scenariuszy:
1// Wszystkie muszą się udać → Promise.all()
2const allResults = await Promise.all([task1(), task2()]);
3
4// Kontynuuj mimo błędów → Promise.allSettled()
5const mixedResults = await Promise.allSettled([task1(), task2()]);
6
7// Najszybszy wygrywa → Promise.race()
8const fastest = await Promise.race([task1(), timeout(5000)]);
9
10// Pierwszy sukces → Promise.any()
11const firstSuccess = await Promise.any([server1(), server2()]);Ten projekt to Twoja szansa na pokazanie, że opanowałeś asynchroniczność w JavaScript. Podobnie jak w prawdziwym Parku Jurajskim, Twój system musi być odporny na awarie, szybki w reakcji i zdolny do obsługi wielu operacji jednocześnie. Pamiętaj słowa Dr. Iana Malcolma: "Życie znajduje drogę" - a Twój kod musi być na to przygotowany!
Stwórz swój system monitoringu w edytorze poniżej. Zaimplementuj przynajmniej trzy z czterech podsystemów i upewnij się, że system prawidłowo obsługuje błędy oraz wyświetla czytelny raport końcowy.