Kurs JavaScript i TypeScript · Moduł 4: Programowanie asynchroniczne

Model zdarzeń (event loop)

14 min czytania
W tej lekcji8

Witaj ponownie w Parku Jurajskim! Do tej pory skupialiśmy się na podstawach programowania obiektowego - konstrukcji klas, dziedziczeniu i kompozycji. Jednak w realnych aplikacjach, takich jak system zarządzania parkiem pełnym dinozaurów, często musimy radzić sobie z operacjami, które zajmują dużo czasu - od pobierania danych z czujników w wybiegach, komunikacji z systemami bezpieczeństwa, po obsługę interakcji z gośćmi parku.

W tym module przejdziemy do kluczowego aspektu JavaScript: jego asynchronicznej natury i modelu zdarzeń, na którym się opiera. Zrozumienie tego mechanizmu jest niezbędne dla budowania responsywnych i wydajnych aplikacji, które mogą obsługiwać wiele operacji jednocześnie - co jest kluczowe dla bezpieczeństwa parku pełnego drapieżników!

JavaScript - język jednowątkowy z asynchronicznymi możliwościami

JavaScript jest językiem jednowątkowym (single-threaded). Oznacza to, że może wykonywać tylko jedną operację na raz. Jest to podobne do sytuacji, gdy masz tylko jednego pracownika do obsługi wszystkich zadań w parku.

Wyobraź sobie, że ten pracownik (wątek JavaScript) musi:

  • Sprawdzać stan ogrodzeń
  • Monitorować znaki życiowe dinozaurów
  • Odpowiadać na komunikaty od gości
  • Kontrolować systemy awaryjne

Gdyby wykonywał te zadania synchronicznie (jedno po drugim), musiałby całkowicie zakończyć jedno zadanie, zanim przejdzie do następnego. To byłoby niewydajne i potencjalnie niebezpieczne!

Na szczęście, JavaScript ma sprytny system zwany "event loop" (pętlą zdarzeń), który pozwala mu obsługiwać asynchroniczne operacje, mimo że jest jednowątkowy.

Event Loop - serce asynchronicznego JavaScript

Aby zrozumieć event loop, wyobraźmy sobie centrum kontroli Parku Jurajskiego. W tym centrum mamy:

  1. Stos wywołań (Call Stack) - lista aktualnie wykonywanych zadań (główne zadania naszego pracownika)
  2. Kolejka wywołań zwrotnych (Callback Queue) - lista zadań czekających na wykonanie po zakończeniu operacji asynchronicznych
  3. Web APIs (w przeglądarkach) lub C++ APIs (w Node.js) - zewnętrzne systemy, które mogą wykonywać zadania "w tle"
  4. Event Loop - mechanizm, który ciągle sprawdza, czy stos wywołań jest pusty, i jeśli tak, przenosi zadania z kolejki na stos

Zobaczmy, jak to działa na przykładzie zadań w Parku Jurajskim:

1console.log("1. Rozpoczynam poranny obchód parku.");
2
3setTimeout(() => {
4  console.log("4. Otrzymano dane z czujników wybiegów dinozaurów.");
5}, 2000);
6
7fetch('https://jurassic-park-api.com/security-status')
8  .then(response => response.json())
9  .then(data => {
10    console.log("5. Status systemów bezpieczeństwa:", data.status);
11  });
12
13console.log("2. Sprawdzam listę pracowników na dzisiaj.");
14console.log("3. Poranny obchód zakończony.");

Jeśli uruchomimy ten kod, zobaczymy w konsoli:

11. Rozpoczynam poranny obchód parku.
22. Sprawdzam listę pracowników na dzisiaj.
33. Poranny obchód zakończony.
44. Otrzymano dane z czujników wybiegów dinozaurów.
55. Status systemów bezpieczeństwa: aktywny

Co się stało? Przeanalizujmy to krok po kroku:

  1. console.log("1. Rozpoczynam poranny obchód parku.") jest dodawany do stosu wywołań, wykonywany i zdejmowany ze stosu.

  2. setTimeout(...) jest dodawany do stosu wywołań. JavaScript rozpoznaje, że to jest wywołanie asynchroniczne i przekazuje je do Web API, które startuje timer. setTimeout jest zdejmowany ze stosu.

  3. fetch(...) jest dodawany do stosu wywołań. JavaScript rozpoznaje, że to jest wywołanie asynchroniczne i przekazuje je do Web API, które wysyła żądanie HTTP. fetch jest zdejmowany ze stosu.

  4. console.log("2. Sprawdzam listę pracowników na dzisiaj.") jest dodawany do stosu, wykonywany i zdejmowany.

  5. console.log("3. Poranny obchód zakończony.") jest dodawany do stosu, wykonywany i zdejmowany.

  6. Stos wywołań jest teraz pusty, ale mamy dwie operacje asynchroniczne w toku:

    • Timer od setTimeout
    • Żądanie HTTP od fetch
  7. Po 2 sekundach timer kończy odliczanie i callback z setTimeout jest dodawany do kolejki zadań (task queue).

  8. Event loop widzi, że stos wywołań jest pusty, więc przenosi callback z kolejki na stos.

  9. console.log("4. Otrzymano dane z czujników wybiegów dinozaurów.") jest wykonywany i zdejmowany ze stosu.

  10. W międzyczasie żądanie HTTP się kończy, pobierane są dane i callback z .then() jest dodawany do kolejki.

  11. Event loop znów przenosi callback z kolejki na stos.

  12. Dane są przetwarzane i console.log("5. Status systemów bezpieczeństwa:", data.status) jest wykonywany.

Event Loop - szczegółowa analiza

Aby lepiej zrozumieć event loop, narysujmy jego główne komponenty:

1┌─────────────┐    ┌───────────┐    ┌───────────────────┐
2│             │    │           │    │                   │
3│ Call Stack  │    │ Web APIs  │    │  Callback Queue   │
4│             │    │           │    │                   │
5└─────────────┘    └───────────┘    └───────────────────┘
6       ↑                 │                  │
7       │                 ↓                  │
8       │            ┌────────┐              │
9       └────────────│  Event │←─────────────┘
10                    │  Loop  │
11                    └────────┘

W graficznej reprezentacji:

  1. Call Stack - Stos LIFO (Last In, First Out) przechowujący wywołania funkcji, które są obecnie wykonywane
  2. Web APIs - Interfejsy przeglądarki/środowiska dla operacji asynchronicznych (setTimeout, fetch, Event Listeners)
  3. Callback Queue - Kolejka FIFO (First In, First Out) przechowująca callbacki, które są gotowe do wykonania
  4. Event Loop - Ciągłe sprawdzanie, czy stos wywołań jest pusty, i jeśli tak, przenoszenie funkcji z kolejki na stos

Zadziwiające jest to, że cały ten proces działa na jednym wątku! JavaScript nie wykonuje tych zadań równolegle, ale przełącza się między nimi w taki sposób, że wydaje się, jakby wszystko działo się jednocześnie.

Mikrozadania i makrozadania

W nowszych implementacjach JavaScript (i specyfikacji) rozróżniamy dwa rodzaje kolejek:

  1. Kolejka mikrozadań (Microtask Queue) - dla zadań o wyższym priorytecie, jak Promise callbacks
  2. Kolejka makrozadań (Macrotask Queue/Task Queue) - dla zadań o niższym priorytecie, jak setTimeout, setInterval, I/O

Kiedy stos jest pusty, najpierw wykonywane są wszystkie zadania z kolejki mikrozadań, dopiero potem z kolejki makrozadań.

1console.log("1. Rozpoczynam poranne karmienie dinozaurów.");
2
3// Makrozadanie (setTimeout)
4setTimeout(() => {
5  console.log("4. Sprawdzam stan zdrowia dinozaurów.");
6}, 0);
7
8// Mikrozadanie (Promise)
9Promise.resolve().then(() => {
10  console.log("3. Odczyt danych z systemu bezpieczeństwa.");
11});
12
13console.log("2. Przygotowuję pokarm dla drapieżników.");

Wynik:

11. Rozpoczynam poranne karmienie dinozaurów.
22. Przygotowuję pokarm dla drapieżników.
33. Odczyt danych z systemu bezpieczeństwa.
44. Sprawdzam stan zdrowia dinozaurów.

Zauważ, że choć oba zadania asynchroniczne (setTimeout z czasem 0ms i Promise.resolve()) są gotowe do wykonania niemal natychmiast, Promise (mikrozadanie) jest wykonywany przed setTimeout (makrozadaniem).

Blokowanie Event Loop

Ponieważ JavaScript jest jednowątkowy, długotrwałe operacje mogą "zablokować" event loop, zatrzymując wykonywanie innych zadań. To jak sytuacja, gdy pracownik centrum kontroli utknął przy jednym monitorze i nie może sprawdzić pozostałych systemów.

1console.log("Uruchamiam symulację wzrostu dinozaurów...");
2
3// Funkcja blokująca wykonywanie przez 5 sekund
4function heavyComputation() {
5  const startTime = Date.now();
6  while (Date.now() - startTime < 5000) {
7    // Intensywne obliczenia...
8  }
9}
10
11heavyComputation();
12console.log("Symulacja zakończona!"); // Po 5 sekundach
13
14// Ten kod nie wykona się przez 5 sekund, nawet jeśli jest asynchroniczny!
15setTimeout(() => {
16  console.log("Sprawdzam systemy bezpieczeństwa...");
17}, 1000);

W tym przykładzie funkcja heavyComputation() blokuje wątek JavaScript na 5 sekund. W tym czasie żadne inne zadania nie mogą być wykonywane, nawet te asynchroniczne. To jak sytuacja, gdy pracownik parku utknął w jednym zadaniu i nie może reagować na alarmy czy inne ważne sygnały.

W prawdziwym Parku Jurajskim takie blokowanie mogłoby być katastrofalne - raptor mógłby uciec, a system nie zarejestrowałby tego, bo jest zajęty czymś innym!

Dlatego w JavaScript staramy się:

  1. Unikać długich operacji synchronicznych
  2. Dzielić złożone obliczenia na mniejsze części
  3. Wykorzystywać Web Workers do operacji intensywnie wykorzystujących CPU

Kolejność wykonywania zadań - praktyczny przykład

Przeanalizujmy bardziej złożony przykład, który pokazuje kolejność wykonywania różnych typów zadań w Parku Jurajskim:

1// Symulacja systemu kontroli Parku Jurajskiego
2console.log("1. Uruchamianie systemu kontroli Parku Jurajskiego...");
3
4// Makrozadanie (setTimeout)
5setTimeout(() => {
6  console.log("6. Sprawdzanie ogrodzeń elektrycznych...");
7
8  // Zagnieżdżone mikrozadanie w makrozadaniu
9  Promise.resolve().then(() => {
10    console.log("7. Weryfikacja stanu drapieżników...");
11  });
12}, 0);
13
14// Mikrozadanie (Promise)
15Promise.resolve().then(() => {
16  console.log("3. Inicjalizacja kamer bezpieczeństwa...");
17
18  // Zagnieżdżone mikrozadanie
19  Promise.resolve().then(() => {
20    console.log("4. Kalibracja czujników ruchu...");
21  });
22});
23
24// Kolejne mikrozadanie
25Promise.resolve().then(() => {
26  console.log("5. Uruchamianie protokołów awaryjnych...");
27});
28
29// Synchroniczny kod
30console.log("2. Sprawdzanie statusu systemów...");

Wynik:

11. Uruchamianie systemu kontroli Parku Jurajskiego...
22. Sprawdzanie statusu systemów...
33. Inicjalizacja kamer bezpieczeństwa...
44. Kalibracja czujników ruchu...
55. Uruchamianie protokołów awaryjnych...
66. Sprawdzanie ogrodzeń elektrycznych...
77. Weryfikacja stanu drapieżników...

Ten przykład pokazuje, że:

  1. Najpierw wykonywany jest kod synchroniczny (1, 2)
  2. Następnie wykonywane są wszystkie zadania z kolejki mikrozadań (3, 4, 5)
  3. Dopiero potem wykonywane są zadania z kolejki makrozadań (6)
  4. Mikrozadania stworzone podczas wykonywania makrozadania (7) są wykonywane natychmiast po zakończeniu bieżącego makrozadania

Bonus: Var, Let, Const - kiedy używać którego?

Przy pracy z kodem asynchronicznym ważne jest, aby poprawnie deklarować zmienne. Sposób deklaracji wpływa na ich zachowanie w blokach i funkcjach, co ma bezpośredni wpływ na kod asynchroniczny (np. zmienne w pętlach z setTimeout). Podobnie jak w parku mamy różne rodzaje ogrodzeń dla różnych typów dinozaurów (elektryczne dla T-Rexa, standardowe dla Triceratopsa), w JavaScript mamy różne sposoby deklarowania zmiennych: var, let i const.

Const - zawsze domyślny wybór

const oznacza stałą - wartość, która nie może być ponownie przypisana. W Parku Jurajskim używamy const dla rzeczy, które nie powinny się zmieniać, jak parametry bezpieczeństwa czy konfiguracja systemów:

1// Konfiguracja systemu - nie powinna się zmieniać
2const MAX_DINOSAURS_PER_ENCLOSURE = 5;
3const FENCE_MINIMUM_VOLTAGE = 10000; // wolty
4const EMERGENCY_CONTACT = "+1-555-JURASSIC";
5
6// Próba zmiany const spowoduje błąd
7MAX_DINOSAURS_PER_ENCLOSURE = 10; // TypeError: Assignment to constant variable

Kiedy używać const:

  • Dla wartości, które nie będą zmieniane
  • Dla obiektów i tablic (nawet jeśli ich zawartość się zmienia)
  • Jako domyślny wybór - jeśli nie jesteś pewien, użyj const

Ważne: const nie oznacza niezmienności zawartości obiektu lub tablicy!

1// To jest poprawne - modyfikujemy zawartość, nie przypisanie
2const dinosaurs = [];
3dinosaurs.push({ name: "Rexy", species: "T-Rex" }); // OK!
4dinosaurs.push({ name: "Blue", species: "Velociraptor" }); // OK!
5
6const parkConfig = {
7  openTime: "09:00",
8  closeTime: "18:00"
9};
10parkConfig.closeTime = "20:00"; // OK - modyfikujemy właściwość
11
12// To spowoduje błąd - próbujemy ponownie przypisać
13dinosaurs = []; // TypeError!
14parkConfig = {}; // TypeError!

Let - gdy wartość się zmienia

let używamy, gdy wartość zmiennej będzie się zmieniać w trakcie wykonania programu. W Parku Jurajskim używamy let dla rzeczy dynamicznych, jak liczniki, akumulatory czy zmienne pętli:

1// Licznik odwiedzających - zmienia się w ciągu dnia
2let visitorCount = 0;
3
4function addVisitors(count) {
5  visitorCount += count;
6  console.log(`Aktualna liczba odwiedzających: ${visitorCount}`);
7}
8
9addVisitors(50); // 50
10addVisitors(30); // 80
11addVisitors(20); // 100

Kiedy używać let:

  • W pętlach (for, while)
  • Dla liczników i akumulatorów
  • Gdy wartość będzie ponownie przypisywana
1// Pętle - klasyczny przypadek użycia let
2for (let i = 0; i < 5; i++) {
3  console.log(`Sprawdzam dinozaura nr ${i}`);
4}
5
6// Akumulatory
7let totalFeedingCost = 0;
8
9const dinosaurs = [
10  { name: "Rexy", dailyFoodCost: 500 },
11  { name: "Blue", dailyFoodCost: 150 },
12  { name: "Trixie", dailyFoodCost: 200 }
13];
14
15for (const dino of dinosaurs) {
16  totalFeedingCost += dino.dailyFoodCost;
17}
18
19console.log(`Całkowity koszt karmienia: $${totalFeedingCost}`);

Var - prawie nigdy (legacy code)

var to stary sposób deklarowania zmiennych w JavaScript. Ma problematyczne zachowanie i nie powinien być używany w nowoczesnym kodzie. Jedyny czas, gdy zobaczysz var, to w starym kodzie (legacy), który został napisany przed 2015 rokiem (przed ES6).

Problemy z var:

  1. Function scope zamiast block scope - var "wydostaje się" z bloków
1// Problem: var ignoruje bloki
2function checkDinosaur(isDangerous) {
3  if (isDangerous) {
4    var alert = "NIEBEZPIECZEŃSTWO!"; // var jest dostępny poza if!
5  }
6
7  console.log(alert); // "NIEBEZPIECZEŃSTWO!" - przecieka poza if!
8  // Z let/const dostalibyśmy ReferenceError
9}
10
11checkDinosaur(true);
12
13// Porównaj z let:
14function checkDinosaurProper(isDangerous) {
15  if (isDangerous) {
16    let alert = "NIEBEZPIECZEŃSTWO!";
17  }
18
19  console.log(alert); // ReferenceError: alert is not defined
20}
  1. Hoisting z undefined - var jest "podnoszony" na górę funkcji
1// Dziwne zachowanie var
2function feedDinosaurs() {
3  console.log(food); // undefined (nie ReferenceError!)
4  var food = "meat";
5  console.log(food); // "meat"
6}
7
8// To jest interpretowane jako:
9function feedDinosaurs() {
10  var food; // hoisting - deklaracja na górze
11  console.log(food); // undefined
12  food = "meat"; // przypisanie
13  console.log(food); // "meat"
14}
  1. Można redekla rować - można przypadkowo nadpisać zmienne
1var dinosaurName = "Rexy";
2// Przypadkowo używamy tej samej nazwy
3var dinosaurName = "Blue"; // Brak błędu! Nadpisało poprzednią wartość
4
5console.log(dinosaurName); // "Blue" - Rexy zniknął!
6
7// Z let/const:
8let dinoName = "Rexy";
9let dinoName = "Blue"; // SyntaxError: Identifier 'dinoName' has already been declared

Kiedy używać var:

  • Nigdy w nowym kodzie
  • Tylko gdy edytujesz bardzo stary kod i musisz zachować kompatybilność

Praktyczny przykład - Code Review

Wyobraź sobie, że dostajesz kod od kolegi do przejrzenia. Oto jak poprawić użycie zmiennych:

Zły kod (przed):

1// Źle - wszystko na var
2var parkName = "Jurassic Park"; // Nie zmienia się - powinno być const
3var isOpen = true; // Zmienia się - powinno być let
4var maxCapacity = 10000; // Nie zmienia się - powinno być const
5
6function processVisitors() {
7  var totalVisitors = 0; // Akumulator - powinno być let
8
9  for (var i = 0; i < 100; i++) { // Iterator - powinno być let
10    totalVisitors++;
11  }
12
13  console.log(i); // 100 - var przecieka! Problem!
14  return totalVisitors;
15}

Dobry kod (po):

1// Dobrze - używamy const jako domyślnego
2const parkName = "Jurassic Park";
3const maxCapacity = 10000;
4
5// let tylko gdy wartość się zmienia
6let isOpen = true;
7
8function processVisitors() {
9  // let dla akumulatora
10  let totalVisitors = 0;
11
12  // let w pętli - block scoped
13  for (let i = 0; i < 100; i++) {
14    totalVisitors++;
15  }
16
17  // console.log(i); // ReferenceError - i jest w scope pętli
18  return totalVisitors;
19}
20
21// Możemy zmieniać isOpen
22isOpen = false;
23isOpen = true;
24
25// Ale nie możemy zmienić const
26// parkName = "Dino World"; // TypeError!

Kolejny przykład - System zarządzania dinozaurami

1class DinosaurManagementSystem {
2  constructor() {
3    // const dla właściwości, które nie zmieniają referencji
4    this.dinosaurs = []; // tablica może się zmieniać, ale referencja nie
5    this.config = {     // obiekt może się zmieniać, ale referencja nie
6      maxDinosaurs: 50,
7      securityLevel: "high"
8    };
9  }
10
11  addDinosaur(name, species, dangerLevel) {
12    // const dla parametrów funkcji i lokalnych wartości, które się nie zmieniają
13    const newDinosaur = {
14      id: Date.now(),
15      name,
16      species,
17      dangerLevel,
18      status: "active"
19    };
20
21    this.dinosaurs.push(newDinosaur);
22    return newDinosaur;
23  }
24
25  getDangerousCount() {
26    // let dla akumulatora
27    let count = 0;
28
29    // const w for...of gdy nie modyfikujemy zmiennej pętli
30    for (const dino of this.dinosaurs) {
31      if (dino.dangerLevel > 7) {
32        count++; // modyfikujemy count, dlatego let
33      }
34    }
35
36    return count;
37  }
38
39  calculateDailyFoodCost() {
40    // let dla wartości, które się zmieniają
41    let totalCost = 0;
42
43    // let w klasycznej pętli for
44    for (let i = 0; i < this.dinosaurs.length; i++) {
45      const dino = this.dinosaurs[i]; // const - nie zmieniamy referencji
46
47      // const dla wartości obliczonych
48      const dailyCost = this.calculateDinosaurFoodCost(dino);
49      totalCost += dailyCost;
50    }
51
52    return totalCost;
53  }
54
55  calculateDinosaurFoodCost(dinosaur) {
56    // const dla wszystkich wartości, które się nie zmieniają
57    const baseCost = 50;
58    const costMultiplier = dinosaur.dangerLevel > 5 ? 2 : 1;
59    const dailyCost = baseCost * costMultiplier;
60
61    return dailyCost;
62  }
63}
64
65// Przykład użycia
66const parkSystem = new DinosaurManagementSystem(); // const - referencja się nie zmienia
67
68parkSystem.addDinosaur("Rexy", "T-Rex", 9);
69parkSystem.addDinosaur("Blue", "Velociraptor", 8);
70parkSystem.addDinosaur("Trixie", "Triceratops", 3);
71
72// let dla wartości, które będą się zmieniać
73let dangerousCount = parkSystem.getDangerousCount();
74console.log(`Niebezpieczne dinozaury: ${dangerousCount}`);
75
76// const dla wartości jednorazowego użycia
77const foodCost = parkSystem.calculateDailyFoodCost();
78console.log(`Dzienny koszt jedzenia: $${foodCost}`);
79
80// Możemy dodać więcej dinozaurów
81parkSystem.addDinosaur("Delta", "Velociraptor", 8);
82
83// I ponownie obliczyć
84dangerousCount = parkSystem.getDangerousCount(); // let pozwala na zmianę
85console.log(`Niebezpieczne dinozaury: ${dangerousCount}`);

Zasada kciuka (Rule of Thumb)

  1. Zawsze zaczyń od const - jeśli kompilator/linter narzeka, że próbujesz zmienić wartość, zmień na let
  2. Użyj let tylko gdy musisz - pętle, liczniki, akumulatory
  3. Nigdy nie używaj var - chyba że edytujesz stary kod
1// Dobra praktyka
2const maxDinosaurs = 50;
3let currentDinosaurs = 0;
4
5for (let i = 0; i < maxDinosaurs; i++) {
6  const dinosaur = createDinosaur();
7  currentDinosaurs++;
8}
9
10// Zła praktyka
11var maxDinosaurs = 50;
12var currentDinosaurs = 0;
13
14for (var i = 0; i < maxDinosaurs; i++) {
15  var dinosaur = createDinosaur();
16  currentDinosaurs++;
17}

Dlaczego to ważne?

Podobnie jak prawidłowe ogrodzenia w Parku Jurajskim zapobiegają ucieczkom dinozaurów, prawidłowe używanie const i let zapobiega błędom w kodzie:

  1. Czytelność - widzisz natychmiast, które wartości się zmieniają (let) a które nie (const)
  2. Bezpieczeństwo - const chroni przed przypadkową modyfikacją
  3. Block scope - zmienne nie "przeciekają" z bloków
  4. Łatwiejsze debugowanie - mniej niespodzianek z hoistingiem

Zapamiętaj: w nowoczesnym JavaScript, var to jak dinozaury - interesujący relikt przeszłości, ale nie chcesz go w swoim produkcyjnym kodzie!

Podsumowanie

Model zdarzeń JavaScript (event loop) jest kluczem do zrozumienia, jak ten jednowątkowy język może obsługiwać wiele operacji asynchronicznych, co jest niezbędne w aplikacjach takich jak system zarządzania Parkiem Jurajskim.

Pamiętaj kluczowe zasady:

  1. JavaScript jest jednowątkowy, ale asynchroniczny
  2. Event loop pozwala na obsługę wielu zadań bez blokowania interfejsu użytkownika
  3. Zadania są wykonywane w określonej kolejności: synchroniczne → mikrozadania → makrozadania
  4. Unikaj blokowania event loop długimi operacjami synchronicznymi

W kolejnych lekcjach zobaczymy, jak wykorzystać tę wiedzę do pisania wydajnego kodu asynchronicznego za pomocą callbacków, Promises i async/await.

Kod do tej lekcji: index.js
1// Event Loop i asynchroniczność w Parku Jurajskim
2console.log("Centrum Kontroli Parku Jurajskiego - Event Loop Demo");
3console.log("Symulacja systemu asynchronicznego zarządzania parkiem\n");
4
5// ===========================================
6// 1. Podstawy Event Loop - Call Stack
7// ===========================================
8console.log("=== 1. CALL STACK - Kolejność wykonywania ===");
9
10function sprawdzOgrodzenie() {
11  console.log("1. Sprawdzam ogrodzenie T-Rexa...");
12  return "Ogrodzenie bezpieczne";
13}
14
15function sprawdzDinozaura() {
16  console.log("2. Sprawdzam stan dinozaura...");
17  const wynik = sprawdzOgrodzenie(); // Synchroniczne wywołanie
18  console.log("3. " + wynik);
19  return "Dinozaur bezpieczny";
20}
21
22function raportBezpieczenstwa() {
23  console.log("0. Rozpoczynam raport bezpieczeństwa");
24  const status = sprawdzDinozaura();
25  console.log("4. Raport: " + status);
26}
27
28// Synchroniczne wykonanie - call stack
29raportBezpieczenstwa();
30
31// ===========================================
32// 2. setTimeout - Callback Queue
33// ===========================================
34console.log("\n=== 2. SETTIMEOUT - Asynchroniczne operacje ===");
35
36console.log("A. Rozpoczynam monitorowanie parku...");
37
38setTimeout(() => {
39  console.log("C. Alarm! Wykryto ruch w zagrodzie T-Rexa!");
40}, 2000);
41
42setTimeout(() => {
43  console.log("D. System automatyczny: Aktywowano dodatkowe zabezpieczenia");
44}, 1000);
45
46console.log("B. Kontynuuję inne zadania...");
47
48// ===========================================
49// 3. Symulacja czujników parku
50// ===========================================
51console.log("\n=== 3. SYMULACJA CZUJNIKÓW PARKU ===");
52
53class DinosaurSensor {
54  constructor(nazwa, dinozaur) {
55    this.nazwa = nazwa;
56    this.dinozaur = dinozaur;
57    this.isActive = true;
58  }
59
60  sprawdzStatus(callback) {
61    console.log(`Czujnik ${this.nazwa}: Sprawdzam ${this.dinozaur}...`);
62
63    // Symulacja opóźnienia czujnika
64    const delay = Math.random() * 3000 + 1000; // 1-4 sekundy
65
66    setTimeout(() => {
67      const status = Math.random() > 0.8 ? 'ALARM' : 'BEZPIECZNY';
68      const raport = {
69        czujnik: this.nazwa,
70        dinozaur: this.dinozaur,
71        status: status,
72        czas: new Date().toLocaleTimeString()
73      };
74      callback(raport);
75    }, delay);
76  }
77}
78
79// Callback hell - problem z zagnieżdżonymi callbackami
80function monitorujPark() {
81  console.log("\nRozpoczynam monitoring parku...");
82
83  const czujnikTRex = new DinosaurSensor("TR-001", "T-Rex");
84  const czujnikVelociraptor = new DinosaurSensor("VR-002", "Velociraptor");
85  const czujnikTriceratops = new DinosaurSensor("TC-003", "Triceratops");
86
87  // Callback hell - każdy czujnik po kolei
88  czujnikTRex.sprawdzStatus((raportTRex) => {
89    console.log("Raport T-Rex:", raportTRex);
90
91    czujnikVelociraptor.sprawdzStatus((raportVelo) => {
92      console.log("Raport Velociraptor:", raportVelo);
93
94      czujnikTriceratops.sprawdzStatus((raportTrice) => {
95        console.log("Raport Triceratops:", raportTrice);
96
97        // Analiza wszystkich raportów
98        const raporty = [raportTRex, raportVelo, raportTrice];
99        const alarmy = raporty.filter(r => r.status === 'ALARM');
100
101        if (alarmy.length > 0) {
102          console.log("UWAGA! Wykryto alarmy:", alarmy.length);
103          alarmy.forEach(alarm => {
104            console.log(`  ${alarm.dinozaur} - ${alarm.status}`);
105          });
106        } else {
107          console.log("Wszystkie dinozaury bezpieczne");
108        }
109      });
110    });
111  });
112}
113
114// Uruchom monitoring po 3 sekundach
115setTimeout(monitorujPark, 3000);
116
117// ===========================================
118// 4. Promises - rozwiązanie Callback Hell
119// ===========================================
120console.log("\n=== 4. PROMISES - Lepsze zarządzanie asynchronicznością ===");
121
122class ModernSensor {
123  constructor(nazwa, dinozaur) {
124    this.nazwa = nazwa;
125    this.dinozaur = dinozaur;
126  }
127
128  sprawdzStatus() {
129    return new Promise((resolve, reject) => {
130      console.log(`Promise: Sprawdzam ${this.dinozaur}...`);
131
132      const delay = Math.random() * 2000 + 500;
133
134      setTimeout(() => {
135        const success = Math.random() > 0.1; // 90% szans na sukces
136
137        if (success) {
138          const status = Math.random() > 0.7 ? 'ALARM' : 'BEZPIECZNY';
139          resolve({
140            czujnik: this.nazwa,
141            dinozaur: this.dinozaur,
142            status: status,
143            czas: new Date().toLocaleTimeString()
144          });
145        } else {
146          reject(new Error(`Czujnik ${this.nazwa} nie odpowiada`));
147        }
148      }, delay);
149    });
150  }
151}
152
153function monitorujParkPromises() {
154  console.log("\nPromise-based monitoring...");
155
156  const modernTRex = new ModernSensor("MTR-001", "Modern T-Rex");
157  const modernVelo = new ModernSensor("MVR-002", "Modern Velociraptor");
158  const modernTrice = new ModernSensor("MTC-003", "Modern Triceratops");
159
160  // Promise chain
161  modernTRex.sprawdzStatus()
162    .then(raport => {
163      console.log("Promise T-Rex:", raport);
164      return modernVelo.sprawdzStatus();
165    })
166    .then(raport => {
167      console.log("Promise Velociraptor:", raport);
168      return modernTrice.sprawdzStatus();
169    })
170    .then(raport => {
171      console.log("Promise Triceratops:", raport);
172      console.log("Wszystkie Promise czujniki sprawdzone!");
173    })
174    .catch(error => {
175      console.log("Błąd w Promise chain:", error.message);
176    });
177
178  // Promise.all - wszystkie jednocześnie
179  console.log("\nPromise.all - równoległe sprawdzanie...");
180
181  Promise.all([
182    modernTRex.sprawdzStatus(),
183    modernVelo.sprawdzStatus(),
184    modernTrice.sprawdzStatus()
185  ])
186  .then(raporty => {
187    console.log("Wszystkie Promise.all raporty gotowe:");
188    raporty.forEach(raport => {
189      console.log(`  ${raport.dinozaur}: ${raport.status}`);
190    });
191
192    const alarmy = raporty.filter(r => r.status === 'ALARM');
193    if (alarmy.length > 0) {
194      console.log(`Promise.all: ${alarmy.length} alarmów!`);
195    } else {
196      console.log("Promise.all: Park bezpieczny!");
197    }
198  })
199  .catch(error => {
200    console.log("Promise.all error:", error.message);
201  });
202}
203
204// Uruchom Promise monitoring po 8 sekundach
205setTimeout(monitorujParkPromises, 8000);
206
207// ===========================================
208// 5. Async/Await - najczystszy sposób
209// ===========================================
210console.log("\n=== 5. ASYNC/AWAIT - Nowoczesny asynchroniczny JavaScript ===");
211
212class AsyncSensor {
213  constructor(nazwa, dinozaur) {
214    this.nazwa = nazwa;
215    this.dinozaur = dinozaur;
216  }
217
218  async sprawdzStatus() {
219    console.log(`Async: Sprawdzam ${this.dinozaur}...`);
220
221    const delay = Math.random() * 1500 + 500;
222
223    // Async/await z Promise
224    return new Promise((resolve, reject) => {
225      setTimeout(() => {
226        const success = Math.random() > 0.05; // 95% sukcesu
227
228        if (success) {
229          const status = Math.random() > 0.6 ? 'ALARM' : 'BEZPIECZNY';
230          resolve({
231            czujnik: this.nazwa,
232            dinozaur: this.dinozaur,
233            status: status,
234            czas: new Date().toLocaleTimeString(),
235            delay: delay.toFixed(0) + 'ms'
236          });
237        } else {
238          reject(new Error(`Async czujnik ${this.nazwa} error`));
239        }
240      }, delay);
241    });
242  }
243}
244
245// Async function
246async function monitorujParkAsync() {
247  console.log("\nAsync/Await monitoring started...");
248
249  try {
250    const asyncTRex = new AsyncSensor("ATR-001", "Async T-Rex");
251    const asyncVelo = new AsyncSensor("AVR-002", "Async Velociraptor");
252    const asyncTrice = new AsyncSensor("ATC-003", "Async Triceratops");
253
254    // Sekwencyjnie (jeden po drugim)
255    console.log("Sekwencyjne sprawdzanie:");
256    const raportTRex = await asyncTRex.sprawdzStatus();
257    console.log("  Async T-Rex:", raportTRex);
258
259    const raportVelo = await asyncVelo.sprawdzStatus();
260    console.log("  Async Velociraptor:", raportVelo);
261
262    const raportTrice = await asyncTrice.sprawdzStatus();
263    console.log("  Async Triceratops:", raportTrice);
264
265    // Równolegle z Promise.all
266    console.log("\nRównoległe sprawdzanie async/await:");
267    const [raportT2, raportV2, raportTr2] = await Promise.all([
268      asyncTRex.sprawdzStatus(),
269      asyncVelo.sprawdzStatus(),
270      asyncTrice.sprawdzStatus()
271    ]);
272
273    const wszystkieRaporty = [raportT2, raportV2, raportTr2];
274    console.log("Wyniki równoległe:");
275    wszystkieRaporty.forEach(raport => {
276      console.log(`  ${raport.dinozaur}: ${raport.status} (${raport.delay})`);
277    });
278
279    // Analiza bezpieczeństwa
280    const niebezpieczne = wszystkieRaporty.filter(r => r.status === 'ALARM');
281    if (niebezpieczne.length > 0) {
282      console.log(`\nASYNC ALERT: ${niebezpieczne.length} dinozaurów wymaga uwagi!`);
283      niebezpieczne.forEach(dino => {
284        console.log(`  ${dino.dinozaur} - wymagana interwencja`);
285      });
286    } else {
287      console.log("\nASYNC SUCCESS: Wszystkie dinozaury pod kontrolą!");
288    }
289
290  } catch (error) {
291    console.log("Async/await error:", error.message);
292  }
293}
294
295// Uruchom async monitoring po 15 sekundach
296setTimeout(monitorujParkAsync, 15000);
297
298// ===========================================
299// 6. Event Loop w praktyce - Mikrotaski vs Makrotaski
300// ===========================================
301console.log("\n=== 6. EVENT LOOP - Kolejność wykonywania ===");
302
303function demonstracjaEventLoop() {
304  console.log("\nEvent Loop demo - kolejność wykonywania:");
305
306  console.log("1. Synchroniczny kod");
307
308  setTimeout(() => {
309    console.log("4. setTimeout (Macro task)");
310  }, 0);
311
312  Promise.resolve().then(() => {
313    console.log("3. Promise.then (Micro task)");
314  });
315
316  console.log("2. Synchroniczny kod - koniec");
317
318  // Kolejność: 1, 2, 3, 4
319  // Microtasks (Promise) mają wyższy priorytet niż macrotasks (setTimeout)
320}
321
322demonstracjaEventLoop();
323
324// ===========================================
325// 7. Praktyczny przykład - System alertów parku
326// ===========================================
327console.log("\n=== 7. SYSTEM ALERTÓW PARKU ===");
328
329class ParkAlertSystem {
330  constructor() {
331    this.alerts = [];
332    this.isMonitoring = false;
333  }
334
335  async startMonitoring() {
336    this.isMonitoring = true;
337    console.log("System alertów rozpoczął monitoring...");
338
339    while (this.isMonitoring) {
340      await this.sprawdzSystemy();
341      await this.czekaj(2000); // Sprawdzaj co 2 sekundy
342    }
343  }
344
345  stopMonitoring() {
346    this.isMonitoring = false;
347    console.log("⏹Monitoring zatrzymany");
348  }
349
350  async sprawdzSystemy() {
351    const systemy = ['ogrodzenia', 'zasilanie', 'komunikacja', 'bezpieczeństwo'];
352
353    const obietnice = systemy.map(system => this.sprawdzSystem(system));
354
355    try {
356      const wyniki = await Promise.allSettled(obietnice);
357
358      wyniki.forEach((wynik, index) => {
359        const systemName = systemy[index];
360        if (wynik.status === 'fulfilled') {
361          if (wynik.value.status === 'ALARM') {
362            this.dodajAlert(systemName, wynik.value.message);
363          }
364        } else {
365          this.dodajAlert(systemName, `Błąd systemu: ${wynik.reason}`);
366        }
367      });
368
369      if (this.alerts.length > 0) {
370        this.wyswietlAlertyIWyczysc();
371      }
372
373    } catch (error) {
374      console.log("Krytyczny błąd monitoringu:", error);
375    }
376  }
377
378  async sprawdzSystem(system) {
379    const delay = Math.random() * 1000 + 200;
380
381    return new Promise((resolve, reject) => {
382      setTimeout(() => {
383        const isWorking = Math.random() > 0.2; // 80% szans na działanie
384
385        if (isWorking) {
386          const hasAlert = Math.random() > 0.8; // 20% szans na alert
387          resolve({
388            system,
389            status: hasAlert ? 'ALARM' : 'OK',
390            message: hasAlert ? `Problem z systemem ${system}` : `System ${system} działa poprawnie`
391          });
392        } else {
393          reject(`System ${system} nie odpowiada`);
394        }
395      }, delay);
396    });
397  }
398
399  dodajAlert(system, message) {
400    this.alerts.push({
401      system,
402      message,
403      time: new Date().toLocaleTimeString()
404    });
405  }
406
407  wyswietlAlertyIWyczysc() {
408    console.log(`\nALERT PARKU (${this.alerts.length} problemów):`);
409    this.alerts.forEach(alert => {
410      console.log(`  [${alert.time}] ${alert.system}: ${alert.message}`);
411    });
412    this.alerts = []; // Wyczyść po wyświetleniu
413  }
414
415  async czekaj(ms) {
416    return new Promise(resolve => setTimeout(resolve, ms));
417  }
418}
419
420// Test systemu alertów
421const alertSystem = new ParkAlertSystem();
422
423// Uruchom system po 20 sekundach na 10 sekund
424setTimeout(async () => {
425  await alertSystem.startMonitoring();
426
427  // Zatrzymaj po 10 sekundach
428  setTimeout(() => {
429    alertSystem.stopMonitoring();
430  }, 10000);
431}, 20000);
432
433// ===========================================
434// 8. Podsumowanie Event Loop
435// ===========================================
436setTimeout(() => {
437  console.log("\n" + "=".repeat(50));
438  console.log("PODSUMOWANIE EVENT LOOP:");
439  console.log("1. JavaScript jest jednowątkowy");
440  console.log("2. Event Loop pozwala na asynchroniczność");
441  console.log("3. Callbacks → Promises → Async/Await");
442  console.log("4. Microtasks mają wyższy priorytet niż Macrotasks");
443  console.log("5. Async/Await to najczystsza składnia");
444  console.log("=".repeat(50));
445}, 35000);

Widzisz błąd w tej lekcji?

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. Co to jest Event Loop w JavaScript?

Przydatne artykuły