Kurs JavaScript i TypeScript · Moduł 4: Programowanie asynchroniczne
Model zdarzeń (event loop)
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:
- Stos wywołań (Call Stack) - lista aktualnie wykonywanych zadań (główne zadania naszego pracownika)
- Kolejka wywołań zwrotnych (Callback Queue) - lista zadań czekających na wykonanie po zakończeniu operacji asynchronicznych
- Web APIs (w przeglądarkach) lub C++ APIs (w Node.js) - zewnętrzne systemy, które mogą wykonywać zadania "w tle"
- 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: aktywnyCo się stało? Przeanalizujmy to krok po kroku:
console.log("1. Rozpoczynam poranny obchód parku.")jest dodawany do stosu wywołań, wykonywany i zdejmowany ze stosu.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.setTimeoutjest zdejmowany ze stosu.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.fetchjest zdejmowany ze stosu.console.log("2. Sprawdzam listę pracowników na dzisiaj.")jest dodawany do stosu, wykonywany i zdejmowany.console.log("3. Poranny obchód zakończony.")jest dodawany do stosu, wykonywany i zdejmowany.Stos wywołań jest teraz pusty, ale mamy dwie operacje asynchroniczne w toku:
- Timer od
setTimeout - Żądanie HTTP od
fetch
- Timer od
Po 2 sekundach timer kończy odliczanie i callback z
setTimeoutjest dodawany do kolejki zadań (task queue).Event loop widzi, że stos wywołań jest pusty, więc przenosi callback z kolejki na stos.
console.log("4. Otrzymano dane z czujników wybiegów dinozaurów.")jest wykonywany i zdejmowany ze stosu.W międzyczasie żądanie HTTP się kończy, pobierane są dane i callback z
.then()jest dodawany do kolejki.Event loop znów przenosi callback z kolejki na stos.
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:
- Call Stack - Stos LIFO (Last In, First Out) przechowujący wywołania funkcji, które są obecnie wykonywane
- Web APIs - Interfejsy przeglądarki/środowiska dla operacji asynchronicznych (setTimeout, fetch, Event Listeners)
- Callback Queue - Kolejka FIFO (First In, First Out) przechowująca callbacki, które są gotowe do wykonania
- 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:
- Kolejka mikrozadań (Microtask Queue) - dla zadań o wyższym priorytecie, jak Promise callbacks
- 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ę:
- Unikać długich operacji synchronicznych
- Dzielić złożone obliczenia na mniejsze części
- 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:
- Najpierw wykonywany jest kod synchroniczny (1, 2)
- Następnie wykonywane są wszystkie zadania z kolejki mikrozadań (3, 4, 5)
- Dopiero potem wykonywane są zadania z kolejki makrozadań (6)
- 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 variableKiedy 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); // 100Kiedy 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:
- 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}- Hoisting z undefined -
varjest "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}- 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 declaredKiedy 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)
- Zawsze zaczyń od
const- jeśli kompilator/linter narzeka, że próbujesz zmienić wartość, zmień nalet - Użyj
lettylko gdy musisz - pętle, liczniki, akumulatory - 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:
- Czytelność - widzisz natychmiast, które wartości się zmieniają (
let) a które nie (const) - Bezpieczeństwo -
constchroni przed przypadkową modyfikacją - Block scope - zmienne nie "przeciekają" z bloków
- Ł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:
- JavaScript jest jednowątkowy, ale asynchroniczny
- Event loop pozwala na obsługę wielu zadań bez blokowania interfejsu użytkownika
- Zadania są wykonywane w określonej kolejności: synchroniczne → mikrozadania → makrozadania
- 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. Co to jest Event Loop w JavaScript?