JavaScript and TypeScript course Β· Module 4: Asynchronous JavaScript

Event Loop (Event Model)

15 min read
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:

  1. Call Stack - a list of currently executing tasks (the main tasks of our employee)
  2. Callback Queue - a list of tasks waiting to be executed after asynchronous operations complete
  3. Web APIs (in browsers) or C++ APIs (in Node.js) - external systems that can perform tasks "in the background"
  4. 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: active

What happened? Let's analyze it step by step:

  1. console.log("1. Starting the morning park walkthrough.") is added to the call stack, executed, and removed from the stack.

  2. 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. setTimeout is removed from the stack.

  3. 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. fetch is removed from the stack.

  4. console.log("2. Checking today's staff roster.") is added to the stack, executed, and removed.

  5. console.log("3. Morning walkthrough complete.") is added to the stack, executed, and removed.

  6. The call stack is now empty, but we have two asynchronous operations in progress:

    • The timer from setTimeout
    • The HTTP request from fetch
  7. After 2 seconds the timer expires and the setTimeout callback is added to the task queue.

  8. The event loop sees the call stack is empty, so it moves the callback from the queue to the stack.

  9. console.log("4. Received data from dinosaur enclosure sensors.") is executed and removed from the stack.

  10. Meanwhile the HTTP request completes, the data is fetched, and the .then() callback is added to the queue.

  11. The event loop moves the callback from the queue to the stack again.

  12. 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:

  1. Call Stack - a LIFO (Last In, First Out) stack storing function calls currently being executed
  2. Web APIs - browser/environment interfaces for asynchronous operations (setTimeout, fetch, Event Listeners)
  3. Callback Queue - a FIFO (First In, First Out) queue storing callbacks ready to execute
  4. 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:

  1. Microtask Queue - for higher-priority tasks, such as Promise callbacks
  2. 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:

  1. Avoid long synchronous operations
  2. Split complex computations into smaller pieces
  3. 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:

  1. Synchronous code executes first (1, 2)
  2. Then all microtask queue tasks execute (3, 4, 5)
  3. Only then do macrotask queue tasks execute (6)
  4. 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 variable

When 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); // 100

When 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:

  1. 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}
  1. Hoisting with undefined - var is "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}
  1. 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 declared

When 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

  1. Always start with const - if the compiler/linter complains that you are trying to change the value, switch to let
  2. Use let only when you have to - loops, counters, accumulators
  3. 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:

  1. Readability - you can immediately see which values change (let) and which do not (const)
  2. Safety - const protects against accidental modification
  3. Block scope - variables do not "leak" out of blocks
  4. 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:

  1. JavaScript is single-threaded but asynchronous
  2. The event loop allows handling many tasks without blocking the user interface
  3. Tasks execute in a defined order: synchronous β†’ microtasks β†’ macrotasks
  4. 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. 1. What is the Event Loop in JavaScript?

Useful articles