JavaScript and TypeScript course Β· Module 4: Asynchronous JavaScript
Event Loop (Event Model)
In this lesson8
Welcome back to Jurassic Park! So far we have focused on the basics of object-oriented programming - building classes, inheritance, and composition. However, in real applications like a dinosaur park management system, we often have to deal with operations that take a lot of time - from fetching data from sensors in enclosures, communicating with security systems, to handling park visitor interactions.
In this module we will move on to a key aspect of JavaScript: its asynchronous nature and the event model it is built on. Understanding this mechanism is essential for building responsive and efficient applications that can handle many operations simultaneously - which is critical for the safety of a park full of predators!
JavaScript - A Single-Threaded Language with Asynchronous Capabilities
JavaScript is a single-threaded language. This means it can execute only one operation at a time. It is similar to having only one employee to handle all tasks in the park.
Imagine that this employee (the JavaScript thread) must:
- Check the status of the fences
- Monitor dinosaur vital signs
- Respond to messages from guests
- Control emergency systems
If they performed these tasks synchronously (one after another), they would have to fully complete one task before moving on to the next. That would be inefficient and potentially dangerous!
Fortunately, JavaScript has a clever system called the "event loop" that allows it to handle asynchronous operations despite being single-threaded.
Event Loop - The Heart of Asynchronous JavaScript
To understand the event loop, let's imagine the Jurassic Park control center. In this center we have:
- Call Stack - a list of currently executing tasks (the main tasks of our employee)
- Callback Queue - a list of tasks waiting to be executed after asynchronous operations complete
- Web APIs (in browsers) or C++ APIs (in Node.js) - external systems that can perform tasks "in the background"
- Event Loop - a mechanism that continuously checks whether the call stack is empty, and if so, moves tasks from the queue to the stack
Let's see how this works with a Jurassic Park example:
1console.log("1. Starting the morning park walkthrough.");
2
3setTimeout(() => {
4 console.log("4. Received data from dinosaur enclosure sensors.");
5}, 2000);
6
7fetch('https://jurassic-park-api.com/security-status')
8 .then(response => response.json())
9 .then(data => {
10 console.log("5. Security system status:", data.status);
11 });
12
13console.log("2. Checking today's staff roster.");
14console.log("3. Morning walkthrough complete.");If we run this code, we will see in the console:
11. Starting the morning park walkthrough.
22. Checking today's staff roster.
33. Morning walkthrough complete.
44. Received data from dinosaur enclosure sensors.
55. Security system status: activeWhat happened? Let's analyze it step by step:
console.log("1. Starting the morning park walkthrough.")is added to the call stack, executed, and removed from the stack.setTimeout(...)is added to the call stack. JavaScript recognizes this is an asynchronous call and passes it to the Web API, which starts the timer.setTimeoutis removed from the stack.fetch(...)is added to the call stack. JavaScript recognizes this is an asynchronous call and passes it to the Web API, which sends the HTTP request.fetchis removed from the stack.console.log("2. Checking today's staff roster.")is added to the stack, executed, and removed.console.log("3. Morning walkthrough complete.")is added to the stack, executed, and removed.The call stack is now empty, but we have two asynchronous operations in progress:
- The timer from
setTimeout - The HTTP request from
fetch
- The timer from
After 2 seconds the timer expires and the
setTimeoutcallback is added to the task queue.The event loop sees the call stack is empty, so it moves the callback from the queue to the stack.
console.log("4. Received data from dinosaur enclosure sensors.")is executed and removed from the stack.Meanwhile the HTTP request completes, the data is fetched, and the
.then()callback is added to the queue.The event loop moves the callback from the queue to the stack again.
The data is processed and
console.log("5. Security system status:", data.status)is executed.
Event Loop - Detailed Analysis
To better understand the event loop, let's draw its main components:
1βββββββββββββββ βββββββββββββ βββββββββββββββββββββ
2β β β β β β
3β Call Stack β β Web APIs β β Callback Queue β
4β β β β β β
5βββββββββββββββ βββββββββββββ βββββββββββββββββββββ
6 β β β
7 β β β
8 β ββββββββββ β
9 ββββββββββββββ Event ββββββββββββββββ
10 β Loop β
11 ββββββββββIn the graphical representation:
- Call Stack - a LIFO (Last In, First Out) stack storing function calls currently being executed
- Web APIs - browser/environment interfaces for asynchronous operations (setTimeout, fetch, Event Listeners)
- Callback Queue - a FIFO (First In, First Out) queue storing callbacks ready to execute
- Event Loop - continuously checks whether the call stack is empty, and if so, moves functions from the queue to the stack
The amazing thing is that all of this runs on a single thread! JavaScript does not execute these tasks in parallel, but switches between them in a way that appears as if everything is happening simultaneously.
Microtasks and Macrotasks
In newer JavaScript implementations (and the specification) we distinguish two types of queues:
- Microtask Queue - for higher-priority tasks, such as Promise callbacks
- Macrotask Queue / Task Queue - for lower-priority tasks, such as setTimeout, setInterval, I/O
When the stack is empty, all tasks from the microtask queue are executed first, then from the macrotask queue.
1console.log("1. Starting the morning dinosaur feeding.");
2
3// Macrotask (setTimeout)
4setTimeout(() => {
5 console.log("4. Checking dinosaur health status.");
6}, 0);
7
8// Microtask (Promise)
9Promise.resolve().then(() => {
10 console.log("3. Reading data from the security system.");
11});
12
13console.log("2. Preparing food for the predators.");Result:
11. Starting the morning dinosaur feeding.
22. Preparing food for the predators.
33. Reading data from the security system.
44. Checking dinosaur health status.Notice that although both asynchronous tasks (setTimeout with 0ms and Promise.resolve()) are ready to execute almost immediately, the Promise (microtask) executes before the setTimeout (macrotask).
Blocking the Event Loop
Because JavaScript is single-threaded, long-running operations can "block" the event loop, stopping other tasks from executing. It is like the situation where the control center worker gets stuck at one monitor and cannot check the other systems.
1console.log("Starting dinosaur growth simulation...");
2
3// Function that blocks execution for 5 seconds
4function heavyComputation() {
5 const startTime = Date.now();
6 while (Date.now() - startTime < 5000) {
7 // Intensive computations...
8 }
9}
10
11heavyComputation();
12console.log("Simulation complete!"); // After 5 seconds
13
14// This code will not execute for 5 seconds, even though it is asynchronous!
15setTimeout(() => {
16 console.log("Checking security systems...");
17}, 1000);In this example the heavyComputation() function blocks the JavaScript thread for 5 seconds. During this time no other tasks can be executed, including asynchronous ones. It is like the park worker getting stuck on one task and not being able to respond to alarms or other important signals.
In a real Jurassic Park such blocking could be catastrophic - a raptor could escape and the system would not register it because it is busy with something else!
That is why in JavaScript we try to:
- Avoid long synchronous operations
- Split complex computations into smaller pieces
- Use Web Workers for CPU-intensive operations
Task Execution Order - Practical Example
Let's analyze a more complex example that shows the order of execution of different task types in Jurassic Park:
1// Jurassic Park control system simulation
2console.log("1. Starting the Jurassic Park control system...");
3
4// Macrotask (setTimeout)
5setTimeout(() => {
6 console.log("6. Checking electric fences...");
7
8 // Nested microtask inside macrotask
9 Promise.resolve().then(() => {
10 console.log("7. Verifying predator status...");
11 });
12}, 0);
13
14// Microtask (Promise)
15Promise.resolve().then(() => {
16 console.log("3. Initializing security cameras...");
17
18 // Nested microtask
19 Promise.resolve().then(() => {
20 console.log("4. Calibrating motion sensors...");
21 });
22});
23
24// Another microtask
25Promise.resolve().then(() => {
26 console.log("5. Starting emergency protocols...");
27});
28
29// Synchronous code
30console.log("2. Checking system status...");Result:
11. Starting the Jurassic Park control system...
22. Checking system status...
33. Initializing security cameras...
44. Calibrating motion sensors...
55. Starting emergency protocols...
66. Checking electric fences...
77. Verifying predator status...This example shows that:
- Synchronous code executes first (1, 2)
- Then all microtask queue tasks execute (3, 4, 5)
- Only then do macrotask queue tasks execute (6)
- Microtasks created during a macrotask (7) execute immediately after the current macrotask finishes
Bonus: Var, Let, Const - When to Use Which?
When working with asynchronous code it is important to declare variables correctly. The way of declaration affects their behavior in blocks and functions, which directly impacts asynchronous code (e.g., variables in loops with setTimeout). Just as the park has different types of fences for different dinosaur types (electric for T-Rex, standard for Triceratops), in JavaScript we have different ways to declare variables: var, let, and const.
Const - Always the Default Choice
const means a constant - a value that cannot be reassigned. In Jurassic Park we use const for things that should not change, like safety parameters or system configuration:
1// System configuration - should not change
2const MAX_DINOSAURS_PER_ENCLOSURE = 5;
3const FENCE_MINIMUM_VOLTAGE = 10000; // volts
4const EMERGENCY_CONTACT = "+1-555-JURASSIC";
5
6// Trying to change a const will throw an error
7MAX_DINOSAURS_PER_ENCLOSURE = 10; // TypeError: Assignment to constant variableWhen to use const:
- For values that will not change
- For objects and arrays (even if their contents change)
- As the default choice - when in doubt, use
const
Important: const does not mean the contents of an object or array are immutable!
1// This is fine - we modify the contents, not the assignment
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 - modifying a property
11
12// This will throw an error - trying to reassign
13dinosaurs = []; // TypeError!
14parkConfig = {}; // TypeError!Let - When the Value Changes
let is used when a variable's value will change during program execution. In Jurassic Park we use let for dynamic things like counters, accumulators, or loop variables:
1// Visitor counter - changes throughout the day
2let visitorCount = 0;
3
4function addVisitors(count) {
5 visitorCount += count;
6 console.log(`Current visitor count: ${visitorCount}`);
7}
8
9addVisitors(50); // 50
10addVisitors(30); // 80
11addVisitors(20); // 100When to use let:
- In loops (for, while)
- For counters and accumulators
- When the value will be reassigned
1// Loops - classic use case for let
2for (let i = 0; i < 5; i++) {
3 console.log(`Checking dinosaur #${i}`);
4}
5
6// Accumulators
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(`Total feeding cost: $${totalFeedingCost}`);Var - Almost Never (Legacy Code)
var is the old way of declaring variables in JavaScript. It has problematic behavior and should not be used in modern code. The only time you will see var is in old (legacy) code written before 2015 (before ES6).
Problems with var:
- Function scope instead of block scope -
var"escapes" from blocks
1// Problem: var ignores blocks
2function checkDinosaur(isDangerous) {
3 if (isDangerous) {
4 var alert = "DANGER!"; // var is accessible outside the if block!
5 }
6
7 console.log(alert); // "DANGER!" - leaks out of the if block!
8 // With let/const we would get a ReferenceError
9}
10
11checkDinosaur(true);
12
13// Compare with let:
14function checkDinosaurProper(isDangerous) {
15 if (isDangerous) {
16 let alert = "DANGER!";
17 }
18
19 console.log(alert); // ReferenceError: alert is not defined
20}- Hoisting with undefined -
varis "hoisted" to the top of the function
1// Strange var behavior
2function feedDinosaurs() {
3 console.log(food); // undefined (not ReferenceError!)
4 var food = "meat";
5 console.log(food); // "meat"
6}
7
8// This is interpreted as:
9function feedDinosaurs() {
10 var food; // hoisting - declaration at the top
11 console.log(food); // undefined
12 food = "meat"; // assignment
13 console.log(food); // "meat"
14}- Can be redeclared - you can accidentally overwrite variables
1var dinosaurName = "Rexy";
2// Accidentally using the same name
3var dinosaurName = "Blue"; // No error! Overwrote the previous value
4
5console.log(dinosaurName); // "Blue" - Rexy is gone!
6
7// With let/const:
8let dinoName = "Rexy";
9let dinoName = "Blue"; // SyntaxError: Identifier 'dinoName' has already been declaredWhen to use var:
- Never in new code
- Only when editing very old code and you must maintain compatibility
Practical Example - Code Review
Imagine you receive code from a colleague for review. Here is how to improve variable usage:
Bad code (before):
1// Bad - everything with var
2var parkName = "Jurassic Park"; // Does not change - should be const
3var isOpen = true; // Changes - should be let
4var maxCapacity = 10000; // Does not change - should be const
5
6function processVisitors() {
7 var totalVisitors = 0; // Accumulator - should be let
8
9 for (var i = 0; i < 100; i++) { // Iterator - should be let
10 totalVisitors++;
11 }
12
13 console.log(i); // 100 - var leaks! Problem!
14 return totalVisitors;
15}Good code (after):
1// Good - using const as default
2const parkName = "Jurassic Park";
3const maxCapacity = 10000;
4
5// let only when the value changes
6let isOpen = true;
7
8function processVisitors() {
9 // let for accumulator
10 let totalVisitors = 0;
11
12 // let in loop - block scoped
13 for (let i = 0; i < 100; i++) {
14 totalVisitors++;
15 }
16
17 // console.log(i); // ReferenceError - i is scoped to the loop
18 return totalVisitors;
19}
20
21// We can change isOpen
22isOpen = false;
23isOpen = true;
24
25// But we cannot change const
26// parkName = "Dino World"; // TypeError!Another Example - Dinosaur Management System
1class DinosaurManagementSystem {
2 constructor() {
3 // const for properties whose references do not change
4 this.dinosaurs = []; // array contents may change, but reference does not
5 this.config = { // object contents may change, but reference does not
6 maxDinosaurs: 50,
7 securityLevel: "high"
8 };
9 }
10
11 addDinosaur(name, species, dangerLevel) {
12 // const for function parameters and local values that do not change
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 for accumulator
27 let count = 0;
28
29 // const in for...of when we do not modify the loop variable
30 for (const dino of this.dinosaurs) {
31 if (dino.dangerLevel > 7) {
32 count++; // we modify count, hence let
33 }
34 }
35
36 return count;
37 }
38
39 calculateDailyFoodCost() {
40 // let for values that change
41 let totalCost = 0;
42
43 // let in classic for loop
44 for (let i = 0; i < this.dinosaurs.length; i++) {
45 const dino = this.dinosaurs[i]; // const - we do not reassign the reference
46
47 // const for computed values
48 const dailyCost = this.calculateDinosaurFoodCost(dino);
49 totalCost += dailyCost;
50 }
51
52 return totalCost;
53 }
54
55 calculateDinosaurFoodCost(dinosaur) {
56 // const for all values that do not change
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// Usage example
66const parkSystem = new DinosaurManagementSystem(); // const - reference does not change
67
68parkSystem.addDinosaur("Rexy", "T-Rex", 9);
69parkSystem.addDinosaur("Blue", "Velociraptor", 8);
70parkSystem.addDinosaur("Trixie", "Triceratops", 3);
71
72// let for values that will change
73let dangerousCount = parkSystem.getDangerousCount();
74console.log(`Dangerous dinosaurs: ${dangerousCount}`);
75
76// const for one-time-use values
77const foodCost = parkSystem.calculateDailyFoodCost();
78console.log(`Daily food cost: $${foodCost}`);
79
80// We can add more dinosaurs
81parkSystem.addDinosaur("Delta", "Velociraptor", 8);
82
83// And recalculate
84dangerousCount = parkSystem.getDangerousCount(); // let allows reassignment
85console.log(`Dangerous dinosaurs: ${dangerousCount}`);Rule of Thumb
- Always start with
const- if the compiler/linter complains that you are trying to change the value, switch tolet - Use
letonly when you have to - loops, counters, accumulators - Never use
var- unless editing old code
1// Good practice
2const maxDinosaurs = 50;
3let currentDinosaurs = 0;
4
5for (let i = 0; i < maxDinosaurs; i++) {
6 const dinosaur = createDinosaur();
7 currentDinosaurs++;
8}
9
10// Bad practice
11var maxDinosaurs = 50;
12var currentDinosaurs = 0;
13
14for (var i = 0; i < maxDinosaurs; i++) {
15 var dinosaur = createDinosaur();
16 currentDinosaurs++;
17}Why Does This Matter?
Just as proper fences in Jurassic Park prevent dinosaur escapes, proper use of const and let prevents bugs in code:
- Readability - you can immediately see which values change (
let) and which do not (const) - Safety -
constprotects against accidental modification - Block scope - variables do not "leak" out of blocks
- Easier debugging - fewer surprises from hoisting
Remember: in modern JavaScript, var is like dinosaurs - an interesting relic of the past, but you do not want it in your production code!
Summary
The JavaScript event model (event loop) is the key to understanding how this single-threaded language can handle many asynchronous operations - essential in applications like the Jurassic Park management system.
Remember the key principles:
- JavaScript is single-threaded but asynchronous
- The event loop allows handling many tasks without blocking the user interface
- Tasks execute in a defined order: synchronous β microtasks β macrotasks
- Avoid blocking the event loop with long synchronous operations
In the next lessons we will see how to use this knowledge to write efficient asynchronous code using callbacks, Promises, and async/await.
Code for this lesson: index.js
1// Event Loop and asynchronicity in Jurassic Park
2console.log("Jurassic Park Control Center - Event Loop Demo");
3console.log("Simulation of asynchronous park management system\n");
4
5// ===========================================
6// 1. Event Loop basics - Call Stack
7// ===========================================
8console.log("=== 1. CALL STACK - Execution order ===");
9
10function sprawdzFence() {
11 console.log("1. Checking the T-Rex fence...");
12 return "Fence secure";
13}
14
15function sprawdzDinosaura() {
16 console.log("2. Checking the dinosaur's condition...");
17 const wynik = sprawdzFence(); // Synchronous call
18 console.log("3. " + wynik);
19 return "Dinosaur safe";
20}
21
22function raportBezpieczenstwa() {
23 console.log("0. Starting security report");
24 const status = sprawdzDinosaura();
25 console.log("4. Report: " + status);
26}
27
28// Synchronous execution - call stack
29raportBezpieczenstwa();
30
31// ===========================================
32// 2. setTimeout - Callback Queue
33// ===========================================
34console.log("\n=== 2. SETTIMEOUT - Asynchronous operations ===");
35
36console.log("A. Starting park monitoring...");
37
38setTimeout(() => {
39 console.log("C. Alarm! Movement detected in the T-Rex enclosure!");
40}, 2000);
41
42setTimeout(() => {
43 console.log("D. Automatic system: Additional safeguards activated");
44}, 1000);
45
46console.log("B. Continuing other tasks...");
47
48// ===========================================
49// 3. Park sensor simulation
50// ===========================================
51console.log("\n=== 3. PARK SENSOR SIMULATION ===");
52
53class DinosaurSensor {
54 constructor(name, dinosaur) {
55 this.name = name;
56 this.dinosaur = dinosaur;
57 this.isActive = true;
58 }
59
60 sprawdzStatus(callback) {
61 console.log(`Sensor ${this.name}: Checking ${this.dinosaur}...`);
62
63 // Simulating sensor delay
64 const delay = Math.random() * 3000 + 1000; // 1-4 seconds
65
66 setTimeout(() => {
67 const status = Math.random() > 0.8 ? 'ALARM' : 'SAFE';
68 const raport = {
69 czujnik: this.name,
70 dinosaur: this.dinosaur,
71 status: status,
72 czas: new Date().toLocaleTimeString()
73 };
74 callback(raport);
75 }, delay);
76 }
77}
78
79// Callback hell - problem with nested callbacks
80function monitorujPark() {
81 console.log("\nStarting park monitoring...");
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 - each sensor one by one
88 czujnikTRex.sprawdzStatus((raportTRex) => {
89 console.log("T-Rex report:", raportTRex);
90
91 czujnikVelociraptor.sprawdzStatus((raportVelo) => {
92 console.log("Velociraptor report:", raportVelo);
93
94 czujnikTriceratops.sprawdzStatus((raportTrice) => {
95 console.log("Triceratops report:", raportTrice);
96
97 // Analysis of all reports
98 const raporty = [raportTRex, raportVelo, raportTrice];
99 const alarms = raporty.filter(r => r.status === 'ALARM');
100
101 if (alarms.length > 0) {
102 console.log("WARNING! Alarms detected:", alarms.length);
103 alarms.forEach(alarm => {
104 console.log(` ${alarm.dinosaur} - ${alarm.status}`);
105 });
106 } else {
107 console.log("All dinosaurs safe");
108 }
109 });
110 });
111 });
112}
113
114// Start monitoring after 3 seconds
115setTimeout(monitorujPark, 3000);
116
117// ===========================================
118// 4. Promises - solving Callback Hell
119// ===========================================
120console.log("\n=== 4. PROMISES - Better asynchronous management ===");
121
122class ModernSensor {
123 constructor(name, dinosaur) {
124 this.name = name;
125 this.dinosaur = dinosaur;
126 }
127
128 sprawdzStatus() {
129 return new Promise((resolve, reject) => {
130 console.log(`Promise: Checking ${this.dinosaur}...`);
131
132 const delay = Math.random() * 2000 + 500;
133
134 setTimeout(() => {
135 const success = Math.random() > 0.1; // 90% chance of success
136
137 if (success) {
138 const status = Math.random() > 0.7 ? 'ALARM' : 'SAFE';
139 resolve({
140 czujnik: this.name,
141 dinosaur: this.dinosaur,
142 status: status,
143 czas: new Date().toLocaleTimeString()
144 });
145 } else {
146 reject(new Error(`Sensor ${this.name} is not responding`));
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("All Promise sensors checked!");
173 })
174 .catch(error => {
175 console.log("Error in Promise chain:", error.message);
176 });
177
178 // Promise.all - all at once
179 console.log("\nPromise.all - parallel checking...");
180
181 Promise.all([
182 modernTRex.sprawdzStatus(),
183 modernVelo.sprawdzStatus(),
184 modernTrice.sprawdzStatus()
185 ])
186 .then(raporty => {
187 console.log("All Promise.all reports ready:");
188 raporty.forEach(raport => {
189 console.log(` ${raport.dinosaur}: ${raport.status}`);
190 });
191
192 const alarms = raporty.filter(r => r.status === 'ALARM');
193 if (alarms.length > 0) {
194 console.log(`Promise.all: ${alarms.length} alarms!`);
195 } else {
196 console.log("Promise.all: Park safe!");
197 }
198 })
199 .catch(error => {
200 console.log("Promise.all error:", error.message);
201 });
202}
203
204// Start Promise monitoring after 8 seconds
205setTimeout(monitorujParkPromises, 8000);
206
207// ===========================================
208// 5. Async/Await - cleanest approach
209// ===========================================
210console.log("\n=== 5. ASYNC/AWAIT - Modern asynchronous JavaScript ===");
211
212class AsyncSensor {
213 constructor(name, dinosaur) {
214 this.name = name;
215 this.dinosaur = dinosaur;
216 }
217
218 async sprawdzStatus() {
219 console.log(`Async: Checking ${this.dinosaur}...`);
220
221 const delay = Math.random() * 1500 + 500;
222
223 // Async/await with a Promise
224 return new Promise((resolve, reject) => {
225 setTimeout(() => {
226 const success = Math.random() > 0.05; // 95% success
227
228 if (success) {
229 const status = Math.random() > 0.6 ? 'ALARM' : 'SAFE';
230 resolve({
231 czujnik: this.name,
232 dinosaur: this.dinosaur,
233 status: status,
234 czas: new Date().toLocaleTimeString(),
235 delay: delay.toFixed(0) + 'ms'
236 });
237 } else {
238 reject(new Error(`Async sensor ${this.name} 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 // Sequentially (one after another)
255 console.log("Sequential checking:");
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 // Parallel with Promise.all
266 console.log("\nParallel checking async/await:");
267 const [raportT2, raportV2, raportTr2] = await Promise.all([
268 asyncTRex.sprawdzStatus(),
269 asyncVelo.sprawdzStatus(),
270 asyncTrice.sprawdzStatus()
271 ]);
272
273 const wszystkieReporty = [raportT2, raportV2, raportTr2];
274 console.log("Parallel results:");
275 wszystkieReporty.forEach(raport => {
276 console.log(` ${raport.dinosaur}: ${raport.status} (${raport.delay})`);
277 });
278
279 // Security analysis
280 const dangerous = wszystkieReporty.filter(r => r.status === 'ALARM');
281 if (dangerous.length > 0) {
282 console.log(`\nASYNC ALERT: ${dangerous.length} dinosaurs need attention!`);
283 dangerous.forEach(dino => {
284 console.log(` ${dino.dinosaur} - intervention required`);
285 });
286 } else {
287 console.log("\nASYNC SUCCESS: All dinosaurs under control!");
288 }
289
290 } catch (error) {
291 console.log("Async/await error:", error.message);
292 }
293}
294
295// Start async monitoring after 15 seconds
296setTimeout(monitorujParkAsync, 15000);
297
298// ===========================================
299// 6. Event Loop in practice - Microtasks vs Macrotasks
300// ===========================================
301console.log("\n=== 6. EVENT LOOP - Execution order ===");
302
303function demonstracjaEventLoop() {
304 console.log("\nEvent Loop demo - execution order:");
305
306 console.log("1. Synchronous code");
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. Synchronous code - end");
317
318 // Order: 1, 2, 3, 4
319 // Microtasks (Promise) have higher priority than macrotasks (setTimeout)
320}
321
322demonstracjaEventLoop();
323
324// ===========================================
325// 7. Practical example - Park alert system
326// ===========================================
327console.log("\n=== 7. PARK ALERT SYSTEM ===");
328
329class ParkAlertSystem {
330 constructor() {
331 this.alerts = [];
332 this.isMonitoring = false;
333 }
334
335 async startMonitoring() {
336 this.isMonitoring = true;
337 console.log("Alert system started monitoring...");
338
339 while (this.isMonitoring) {
340 await this.sprawdzSystemy();
341 await this.czekaj(2000); // Check every 2 seconds
342 }
343 }
344
345 stopMonitoring() {
346 this.isMonitoring = false;
347 console.log("βΉMonitoring stopped");
348 }
349
350 async sprawdzSystemy() {
351 const systemy = ['fences', 'power', 'communication', 'security'];
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.addAlert(systemName, wynik.value.message);
363 }
364 } else {
365 this.addAlert(systemName, `System error: ${wynik.reason}`);
366 }
367 });
368
369 if (this.alerts.length > 0) {
370 this.wyswietlAlertyIWyczysc();
371 }
372
373 } catch (error) {
374 console.log("Critical monitoring error:", 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% chance of working
384
385 if (isWorking) {
386 const hasAlert = Math.random() > 0.8; // 20% chance of an alert
387 resolve({
388 system,
389 status: hasAlert ? 'ALARM' : 'OK',
390 message: hasAlert ? `Problem with system ${system}` : `System ${system} is working correctly`
391 });
392 } else {
393 reject(`System ${system} is not responding`);
394 }
395 }, delay);
396 });
397 }
398
399 addAlert(system, message) {
400 this.alerts.push({
401 system,
402 message,
403 time: new Date().toLocaleTimeString()
404 });
405 }
406
407 wyswietlAlertyIWyczysc() {
408 console.log(`\nPARK ALERT (${this.alerts.length} problems):`);
409 this.alerts.forEach(alert => {
410 console.log(` [${alert.time}] ${alert.system}: ${alert.message}`);
411 });
412 this.alerts = []; // Clear after display
413 }
414
415 async czekaj(ms) {
416 return new Promise(resolve => setTimeout(resolve, ms));
417 }
418}
419
420// Alert system test
421const alertSystem = new ParkAlertSystem();
422
423// Start the system after 20 seconds for 10 seconds
424setTimeout(async () => {
425 await alertSystem.startMonitoring();
426
427 // Stop after 10 seconds
428 setTimeout(() => {
429 alertSystem.stopMonitoring();
430 }, 10000);
431}, 20000);
432
433// ===========================================
434// 8. Event Loop summary
435// ===========================================
436setTimeout(() => {
437 console.log("\n" + "=".repeat(50));
438 console.log("EVENT LOOP SUMMARY:");
439 console.log("1. JavaScript is single-threaded");
440 console.log("2. The Event Loop enables asynchronicity");
441 console.log("3. Callbacks β Promises β Async/Await");
442 console.log("4. Microtasks have higher priority than Macrotasks");
443 console.log("5. Async/Await is the cleanest syntax");
444 console.log("=".repeat(50));
445}, 35000);Spotted a mistake in this lesson?
Check yourself
Answer the questions from this lesson. Pick an answer to see right away whether it is correct.
1. What is the Event Loop in JavaScript?