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 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:
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.
To understand the event loop, let's imagine the Jurassic Park control center. In this center we have:
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. setTimeout is 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. fetch is 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:
setTimeoutfetchAfter 2 seconds the timer expires and the
setTimeout callback 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.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:
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.
In newer JavaScript implementations (and the specification) we distinguish two types of queues:
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).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:
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:
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 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:
constImportant:
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 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:
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 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:
var "escapes" from blocks1// 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}var is "hoisted" to the top of the function1// 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}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:
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!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}`);const — if the compiler/linter complains that you are trying to change the value, switch to letlet only when you have to — loops, counters, accumulatorsvar — unless editing old code1// ✅ 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}Just as proper fences in Jurassic Park prevent dinosaur escapes, proper use of
const and let prevents bugs in code:let) and which do not (const)const protects against accidental modificationRemember: in modern JavaScript,
var is like dinosaurs — an interesting relic of the past, but you do not want it in your production code!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:
In the next lessons we will see how to use this knowledge to write efficient asynchronous code using callbacks, Promises, and async/await.