Utilizziamo i cookie per migliorare la tua esperienza sul sito
CodeWorlds

Projekt końcowy: Asynchroniczny system monitoringu Parku Jurajskiego

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.

Cel projektu

Stworzysz asynchroniczny system monitoringu Parku Jurajskiego składający się z czterech podsystemów:

  1. Moduł czujników - symulacja odczytów z czujników (callbacki)
  2. Moduł komunikacji - wymiana danych między sektorami (Promise)
  3. Moduł analizy zagrożeń - przetwarzanie danych i klasyfikacja alertów (async/await)
  4. Centralny koordynator - równoległe zarządzanie wszystkimi podsystemami (Promise.all/race/allSettled)

Wymagania funkcjonalne

1. Moduł czujników (Callbacki)

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ą.

2. Moduł komunikacji (Promise)

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.

3. Moduł analizy zagrożeń (async/await)

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.

4. Centralny koordynator (Promise.all / race / allSettled)

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();

Wskazówki implementacyjne

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 startowy

Obsł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()]);

Powodzenia!

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.

Vai a CodeWorlds