JavaScript and TypeScript course Β· Module 5: Advanced JavaScript
Implementing an Application Using Learned Techniques
In this lesson4
Welcome to the final phase of our Jurassic Park adventure! In this exercise we'll combine all the techniques we've learned so far to create a complete application for monitoring and managing the Jurassic Park ecosystem. This is our "grand finale" before moving on to structuring larger projects.
Project Goal
We'll create the "JurassicTracker" application - a system for monitoring, tracking, and managing dinosaurs in the park. Our application will use:
- Modular code (ES Modules)
- Asynchronous operations (Promises, async/await)
- Error handling (try/catch, custom error classes)
- Background services (setInterval)
- Application state management
Project Structure
Before we start implementing, let's plan the structure:
1jurassic-tracker/
2βββ index.js # Main application file
3βββ modules/
4β βββ api.js # API communication module
5β βββ dinosaurs.js # Dinosaur management module
6β βββ enclosures.js # Enclosure management module
7β βββ security.js # Security systems module
8β βββ monitoring.js # Park monitoring module
9β βββ utils.js # Utility tools
10β βββ errors/ # Error class directory
11β βββ index.js # Exports all errors
12β βββ APIError.js # API-related errors
13β βββ DinoError.js # Dinosaur-related errors
14β βββ SecurityError.js # Security-related errors
15βββ data/
16 βββ config.js # Application configurationModule Implementations
Let's implement each of the modules. Remember that in practice these files would live in separate files according to the above structure.
1. Errors Module (errors/index.js)
First let's define our custom error classes:
1// errors/APIError.js
2export class APIError extends Error {
3 constructor(message, statusCode, endpoint) {
4 super(message);
5 this.name = 'APIError';
6 this.statusCode = statusCode;
7 this.endpoint = endpoint;
8 this.timestamp = new Date();
9 }
10
11 isServerError() {
12 return this.statusCode >= 500;
13 }
14
15 isClientError() {
16 return this.statusCode >= 400 && this.statusCode < 500;
17 }
18}
19
20// errors/DinoError.js
21export class DinoError extends Error {
22 constructor(message, dinoId, species) {
23 super(message);
24 this.name = 'DinoError';
25 this.dinoId = dinoId;
26 this.species = species;
27 this.timestamp = new Date();
28 }
29}
30
31export class DinoNotFoundError extends DinoError {
32 constructor(dinoId) {
33 super(`Dinosaur with ID ${dinoId} was not found`, dinoId, null);
34 this.name = 'DinoNotFoundError';
35 }
36}
37
38export class DinoBehaviorError extends DinoError {
39 constructor(message, dinoId, species, behaviorType, dangerLevel) {
40 super(message, dinoId, species);
41 this.name = 'DinoBehaviorError';
42 this.behaviorType = behaviorType;
43 this.dangerLevel = dangerLevel; // 1-10
44 }
45
46 requiresIntervention() {
47 return this.dangerLevel >= 7;
48 }
49}
50
51// errors/SecurityError.js
52export class SecurityError extends Error {
53 constructor(message, sector, systemType) {
54 super(message);
55 this.name = 'SecurityError';
56 this.sector = sector;
57 this.systemType = systemType;
58 this.timestamp = new Date();
59 }
60}
61
62export class EnclosureBreachError extends SecurityError {
63 constructor(sector, dinoSpecies, count) {
64 super(`Enclosure breach in sector ${sector}: ${count} ${dinoSpecies} specimens`, sector, 'enclosure');
65 this.name = 'EnclosureBreachError';
66 this.dinoSpecies = dinoSpecies;
67 this.count = count;
68 }
69}
70
71// errors/index.js
72export * from './APIError.js';
73export * from './DinoError.js';
74export * from './SecurityError.js';2. API Module (api.js)
Our API module handles communication with the (hypothetical) Jurassic Park server:
1// modules/api.js
2import { APIError } from '../errors/index.js';
3
4const API_DELAY = 500; // ms - simulate network delay
5const API_ERROR_RATE = 0.1; // 10% chance of API error for realism
6
7// Simulated dinosaur database
8let dinosaursData = [
9 { id: 'trx1', name: 'Rex', species: 'Tyrannosaurus', enclosureId: 'enc1', health: 98, lastFed: new Date(Date.now() - 3600000), behavior: 'calm' },
10 { id: 'rap1', name: 'Blue', species: 'Velociraptor', enclosureId: 'enc2', health: 95, lastFed: new Date(Date.now() - 7200000), behavior: 'agitated' },
11 { id: 'rap2', name: 'Delta', species: 'Velociraptor', enclosureId: 'enc2', health: 92, lastFed: new Date(Date.now() - 7200000), behavior: 'hunting' },
12 { id: 'tri1', name: 'Sarah', species: 'Triceratops', enclosureId: 'enc3', health: 88, lastFed: new Date(Date.now() - 1800000), behavior: 'sleeping' },
13 { id: 'bro1', name: 'Bronty', species: 'Brontosaurus', enclosureId: 'enc4', health: 100, lastFed: new Date(Date.now() - 900000), behavior: 'grazing' },
14];
15
16// Helper function simulating network delay
17const simulateNetworkDelay = () => {
18 return new Promise(resolve => setTimeout(resolve, API_DELAY * (0.5 + Math.random())));
19};
20
21// Helper function simulating API errors
22const simulateApiError = (endpoint) => {
23 if (Math.random() < API_ERROR_RATE) {
24 const errorTypes = [
25 { statusCode: 500, message: 'Internal Server Error' },
26 { statusCode: 503, message: 'Service Unavailable' },
27 { statusCode: 429, message: 'Too Many Requests' },
28 { statusCode: 403, message: 'Forbidden - Insufficient permissions' }
29 ];
30 const errorType = errorTypes[Math.floor(Math.random() * errorTypes.length)];
31 throw new APIError(`API Error: ${errorType.message}`, errorType.statusCode, endpoint);
32 }
33};
34
35// API for fetching dinosaur data
36export async function getDinosaurs() {
37 const endpoint = '/api/dinosaurs';
38 try {
39 await simulateNetworkDelay();
40 simulateApiError(endpoint);
41 return [...dinosaursData]; // Return a copy to avoid mutation
42 } catch (error) {
43 if (error instanceof APIError) throw error;
44 throw new APIError('Error fetching dinosaur data', 500, endpoint);
45 }
46}
47
48export async function getDinosaurById(id) {
49 const endpoint = `/api/dinosaurs/${id}`;
50 try {
51 await simulateNetworkDelay();
52 simulateApiError(endpoint);
53 const dino = dinosaursData.find(d => d.id === id);
54 if (!dino) {
55 throw new APIError(`Dinosaur with ID ${id} not found`, 404, endpoint);
56 }
57 return { ...dino };
58 } catch (error) {
59 if (error instanceof APIError) throw error;
60 throw new APIError('Error fetching dinosaur', 500, endpoint);
61 }
62}3. Dinosaur Module (dinosaurs.js)
This module manages dinosaur data and behavior:
1// modules/dinosaurs.js
2import { getDinosaurs, getDinosaurById, updateDinosaur, logEvent } from './api.js';
3import { DinoNotFoundError, DinoBehaviorError } from '../errors/index.js';
4
5// Assess dinosaur behavior risk
6function assessBehaviorRisk(dino) {
7 const dangerousSpecies = ['Tyrannosaurus', 'Velociraptor', 'Spinosaurus'];
8 const riskBehaviors = {
9 'calm': 1,
10 'grazing': 1,
11 'sleeping': 2,
12 'exploring': 4,
13 'agitated': 6,
14 'hunting': 7,
15 'aggressive': 9
16 };
17
18 // Base risk depends on species
19 let baseRisk = dangerousSpecies.includes(dino.species) ? 5 : 2;
20
21 // Risk modification based on behavior
22 const behaviorRisk = riskBehaviors[dino.behavior] || 3;
23
24 // Risk modification based on time since last feeding
25 const hoursSinceLastFed = (Date.now() - new Date(dino.lastFed).getTime()) / 3600000;
26 const hungerRisk = Math.min(5, Math.floor(hoursSinceLastFed / 3));
27
28 // Total risk level (1-10)
29 const totalRisk = Math.min(10, baseRisk + behaviorRisk + hungerRisk);
30
31 return {
32 risk: totalRisk,
33 factors: {
34 species: baseRisk,
35 behavior: behaviorRisk,
36 hunger: hungerRisk
37 }
38 };
39}
40
41// Get all dinosaurs
42export async function getAllDinosaurs() {
43 try {
44 const dinosaurs = await getDinosaurs();
45
46 // Enrich data with risk assessment
47 return dinosaurs.map(dino => ({
48 ...dino,
49 riskAssessment: assessBehaviorRisk(dino)
50 }));
51 } catch (error) {
52 console.error('Error fetching dinosaur list:', error);
53 throw error;
54 }
55}
56
57// Get a specific dinosaur by ID
58export async function getDinosaur(id) {
59 try {
60 const dino = await getDinosaurById(id);
61 return {
62 ...dino,
63 riskAssessment: assessBehaviorRisk(dino)
64 };
65 } catch (error) {
66 if (error.statusCode === 404) {
67 throw new DinoNotFoundError(id);
68 }
69 console.error(`Error fetching dinosaur ${id}:`, error);
70 throw error;
71 }
72}
73
74// Feed a dinosaur
75export async function feedDinosaur(id) {
76 try {
77 const dino = await getDinosaur(id);
78
79 // Update dinosaur state
80 const updatedDino = await updateDinosaur(id, {
81 lastFed: new Date(),
82 health: Math.min(100, dino.health + 5),
83 behavior: 'calm' // Dinosaurs are calmer after feeding
84 });
85
86 // Log the event
87 await logEvent('FEEDING', {
88 dinoId: id,
89 species: dino.species,
90 name: dino.name,
91 previousFeedingTime: dino.lastFed
92 });
93
94 return {
95 ...updatedDino,
96 riskAssessment: assessBehaviorRisk(updatedDino)
97 };
98 } catch (error) {
99 console.error(`Error feeding dinosaur ${id}:`, error);
100 throw error;
101 }
102}
103
104// Monitor dinosaur behavior
105export async function monitorBehavior(id) {
106 try {
107 const dino = await getDinosaur(id);
108 const { risk, factors } = assessBehaviorRisk(dino);
109
110 // Check if behavior requires intervention
111 if (risk >= 8) {
112 const error = new DinoBehaviorError(
113 `Dangerous behavior from dinosaur ${dino.name}`,
114 id,
115 dino.species,
116 dino.behavior,
117 risk
118 );
119
120 // Log the high-risk event
121 await logEvent('HIGH_RISK_BEHAVIOR', {
122 dinoId: id,
123 species: dino.species,
124 name: dino.name,
125 behavior: dino.behavior,
126 risk,
127 factors
128 });
129
130 throw error;
131 }
132
133 // For medium risk, just log
134 if (risk >= 5) {
135 await logEvent('MEDIUM_RISK_BEHAVIOR', {
136 dinoId: id,
137 species: dino.species,
138 name: dino.name,
139 behavior: dino.behavior,
140 risk,
141 factors
142 });
143 }
144
145 return { dino, risk, factors };
146 } catch (error) {
147 if (error instanceof DinoBehaviorError) {
148 throw error; // Re-throw our custom error
149 }
150 console.error(`Error monitoring dinosaur ${id}:`, error);
151 throw error;
152 }
153}
154
155// Simulate behavior change (used by the monitoring module)
156export async function simulateBehaviorChange() {
157 try {
158 const allDinos = await getAllDinosaurs();
159 const randomIndex = Math.floor(Math.random() * allDinos.length);
160 const targetDino = allDinos[randomIndex];
161
162 // Possible behaviors depending on species and last feeding
163 const carnivores = ['Tyrannosaurus', 'Velociraptor', 'Spinosaurus'];
164
165 let behaviorPool;
166 if (carnivores.includes(targetDino.species)) {
167 // Predators have higher chance of aggressive behavior
168 behaviorPool = ['calm', 'sleeping', 'exploring', 'agitated', 'hunting', 'aggressive'];
169 } else {
170 // Herbivores are generally calmer
171 behaviorPool = ['calm', 'grazing', 'sleeping', 'exploring', 'agitated'];
172 }
173
174 // Hungry dinosaurs are more aggressive
175 const hoursSinceLastFed = (Date.now() - new Date(targetDino.lastFed).getTime()) / 3600000;
176 if (hoursSinceLastFed > 8) {
177 if (carnivores.includes(targetDino.species)) {
178 behaviorPool = ['agitated', 'hunting', 'aggressive'];
179 } else {
180 behaviorPool = ['agitated', 'exploring'];
181 }
182 }
183
184 // Random behavior selection
185 const newBehavior = behaviorPool[Math.floor(Math.random() * behaviorPool.length)];
186
187 // Update dinosaur state
188 const updatedDino = await updateDinosaur(targetDino.id, {
189 behavior: newBehavior
190 });
191
192 await logEvent('BEHAVIOR_CHANGE', {
193 dinoId: targetDino.id,
194 species: targetDino.species,
195 name: targetDino.name,
196 previousBehavior: targetDino.behavior,
197 newBehavior
198 });
199
200 return {
201 ...updatedDino,
202 riskAssessment: assessBehaviorRisk(updatedDino)
203 };
204 } catch (error) {
205 console.error('Error simulating behavior change:', error);
206 throw error;
207 }
208}4. Enclosure Module (enclosures.js)
This module manages enclosures and their security status:
1// modules/enclosures.js
2import { getEnclosures, getEnclosureById, updateEnclosure, logEvent } from './api.js';
3import { SecurityError, EnclosureBreachError } from '../errors/index.js';
4import { getAllDinosaurs } from './dinosaurs.js';
5
6// Assess enclosure security status
7function assessSecurityStatus(enclosure, dinosaurs) {
8 // Check how many dinosaurs are in the enclosure
9 const enclosureDinosaurs = dinosaurs.filter(dino => dino.enclosureId === enclosure.id);
10
11 // Check for carnivores
12 const carnivores = enclosureDinosaurs.filter(dino =>
13 ['Tyrannosaurus', 'Velociraptor', 'Spinosaurus'].includes(dino.species)
14 );
15
16 // Check total risk
17 const totalRisk = enclosureDinosaurs.reduce((sum, dino) =>
18 sum + (dino.riskAssessment ? dino.riskAssessment.risk : 3), 0);
19
20 // Power status
21 const powerStatus = enclosure.powerStatus === 'active' ? 10 :
22 enclosure.powerStatus === 'backup' ? 5 : 0;
23
24 // Time since last inspection (in days)
25 const daysSinceInspection = (Date.now() - new Date(enclosure.lastInspection).getTime()) / (24 * 3600000);
26 const inspectionScore = daysSinceInspection < 1 ? 10 :
27 daysSinceInspection < 3 ? 7 :
28 daysSinceInspection < 7 ? 4 : 1;
29
30 // Calculate overall security score (0-100)
31 const securityScore = Math.max(0, Math.min(100,
32 enclosure.securityLevel * 10 - totalRisk + powerStatus + inspectionScore
33 ));
34
35 return {
36 id: enclosure.id,
37 name: enclosure.name,
38 sector: enclosure.sector,
39 dinosaurCount: enclosureDinosaurs.length,
40 carnivoreCount: carnivores.length,
41 securityLevel: enclosure.securityLevel,
42 powerStatus: enclosure.powerStatus,
43 securityScore,
44 status: securityScore >= 80 ? 'secure' :
45 securityScore >= 60 ? 'normal' :
46 securityScore >= 40 ? 'warning' : 'danger',
47 dinosaurs: enclosureDinosaurs.map(d => ({
48 id: d.id,
49 name: d.name,
50 species: d.species,
51 risk: d.riskAssessment ? d.riskAssessment.risk : 3
52 }))
53 };
54}
55
56// Get all enclosures with security assessment
57export async function getAllEnclosures() {
58 try {
59 const [enclosures, dinosaurs] = await Promise.all([
60 getEnclosures(),
61 getAllDinosaurs()
62 ]);
63
64 // Enrich data with security assessment
65 return enclosures.map(enclosure =>
66 assessSecurityStatus(enclosure, dinosaurs)
67 );
68 } catch (error) {
69 console.error('Error fetching enclosure list:', error);
70 throw error;
71 }
72}
73
74// Inspect an enclosure
75export async function inspectEnclosure(id) {
76 try {
77 const enclosure = await getEnclosureById(id);
78
79 // Update last inspection date
80 const updatedEnclosure = await updateEnclosure(id, {
81 lastInspection: new Date()
82 });
83
84 // Log the event
85 await logEvent('ENCLOSURE_INSPECTION', {
86 enclosureId: id,
87 name: enclosure.name,
88 sector: enclosure.sector,
89 previousInspection: enclosure.lastInspection
90 });
91
92 // Fetch dinosaurs to update security assessment
93 const dinosaurs = await getAllDinosaurs();
94
95 return assessSecurityStatus(updatedEnclosure, dinosaurs);
96 } catch (error) {
97 console.error(`Error inspecting enclosure ${id}:`, error);
98 throw error;
99 }
100}
101
102// Simulate power failure
103export async function simulatePowerFailure() {
104 try {
105 const enclosures = await getEnclosures();
106
107 // Select a random enclosure
108 const randomIndex = Math.floor(Math.random() * enclosures.length);
109 const targetEnclosure = enclosures[randomIndex];
110
111 if (targetEnclosure.powerStatus !== 'active') return;
112
113 // 70% chance of backup power, 30% chance of total failure
114 const newPowerStatus = Math.random() < 0.7 ? 'backup' : 'failure';
115
116 // Update enclosure state
117 const updatedEnclosure = await updateEnclosure(targetEnclosure.id, {
118 powerStatus: newPowerStatus
119 });
120
121 await logEvent('POWER_FAILURE', {
122 enclosureId: targetEnclosure.id,
123 name: targetEnclosure.name,
124 sector: targetEnclosure.sector,
125 powerStatus: newPowerStatus
126 });
127
128 // Check if failure could lead to an enclosure breach
129 if (newPowerStatus === 'failure') {
130 const dinosaurs = await getAllDinosaurs();
131 const enclosureDinos = dinosaurs.filter(dino => dino.enclosureId === targetEnclosure.id);
132
133 const dangerousSpecies = ['Tyrannosaurus', 'Velociraptor', 'Spinosaurus'];
134 const hasDangerousDinos = enclosureDinos.some(dino =>
135 dangerousSpecies.includes(dino.species)
136 );
137
138 if (hasDangerousDinos && targetEnclosure.securityLevel >= 8) {
139 const breachingSpecies = enclosureDinos.find(dino =>
140 dangerousSpecies.includes(dino.species)
141 ).species;
142
143 const breachingCount = enclosureDinos.filter(dino =>
144 dino.species === breachingSpecies
145 ).length;
146
147 throw new EnclosureBreachError(
148 targetEnclosure.sector,
149 breachingSpecies,
150 breachingCount
151 );
152 }
153 }
154
155 const dinosaurs = await getAllDinosaurs();
156 return assessSecurityStatus(updatedEnclosure, dinosaurs);
157 } catch (error) {
158 if (error instanceof EnclosureBreachError) {
159 await logEvent('ENCLOSURE_BREACH', {
160 sector: error.sector,
161 species: error.dinoSpecies,
162 count: error.count,
163 timestamp: error.timestamp
164 });
165 throw error;
166 }
167
168 console.error('Error simulating power failure:', error);
169 throw error;
170 }
171}
172
173// Repair power system
174export async function repairPowerSystem(id) {
175 try {
176 const enclosure = await getEnclosureById(id);
177
178 if (enclosure.powerStatus === 'active') {
179 throw new SecurityError(
180 'Power system is already active',
181 enclosure.sector,
182 'power'
183 );
184 }
185
186 // Update power status
187 const updatedEnclosure = await updateEnclosure(id, {
188 powerStatus: 'active'
189 });
190
191 await logEvent('POWER_RESTORED', {
192 enclosureId: id,
193 name: enclosure.name,
194 sector: enclosure.sector,
195 previousStatus: enclosure.powerStatus
196 });
197
198 const dinosaurs = await getAllDinosaurs();
199
200 return assessSecurityStatus(updatedEnclosure, dinosaurs);
201 } catch (error) {
202 console.error(`Error repairing power system for enclosure ${id}:`, error);
203 throw error;
204 }
205}5. Utility Functions (utils.js)
1// modules/utils.js
2export function formatDate(date) {
3 return new Intl.DateTimeFormat('en-US', {
4 day: '2-digit', month: '2-digit', year: 'numeric',
5 hour: '2-digit', minute: '2-digit'
6 }).format(new Date(date));
7}
8
9export function formatTimeAgo(date) {
10 const diff = Date.now() - new Date(date).getTime();
11 const minutes = Math.floor(diff / 60000);
12 const hours = Math.floor(diff / 3600000);
13
14 if (minutes < 1) return 'just now';
15 if (minutes < 60) return `${minutes} min ago`;
16 if (hours < 24) return `${hours} h ago`;
17 return `${Math.floor(hours / 24)} days ago`;
18}
19
20export async function retry(fn, maxAttempts = 3, delay = 1000) {
21 let lastError;
22 for (let attempt = 1; attempt <= maxAttempts; attempt++) {
23 try {
24 return await fn();
25 } catch (error) {
26 lastError = error;
27 if (attempt < maxAttempts) {
28 console.log(`Attempt ${attempt} failed, retrying in ${delay}ms...`);
29 await new Promise(resolve => setTimeout(resolve, delay));
30 }
31 }
32 }
33 throw lastError;
34}
35
36export function memoize(fn) {
37 const cache = new Map();
38 return async function(...args) {
39 const key = JSON.stringify(args);
40 if (cache.has(key)) {
41 return cache.get(key);
42 }
43 const result = await fn(...args);
44 cache.set(key, result);
45 return result;
46 };
47}6. Monitoring Module (monitoring.js)
1// modules/monitoring.js
2import { getDinosaurs } from './api.js';
3import { DinoBehaviorError, EnclosureBreachError } from '../errors/index.js';
4import { formatTimeAgo } from './utils.js';
5
6class ParkMonitor {
7 constructor() {
8 this.intervals = [];
9 this.alertHandlers = [];
10 this.isRunning = false;
11 }
12
13 onAlert(handler) {
14 this.alertHandlers.push(handler);
15 return () => {
16 this.alertHandlers = this.alertHandlers.filter(h => h !== handler);
17 };
18 }
19
20 _fireAlert(alert) {
21 this.alertHandlers.forEach(handler => handler(alert));
22 }
23
24 async checkDinosaurStatuses() {
25 try {
26 const dinosaurs = await getDinosaurs();
27
28 for (const dino of dinosaurs) {
29 const hoursSinceFeeding = (Date.now() - new Date(dino.lastFed).getTime()) / 3600000;
30
31 if (hoursSinceFeeding > 8) {
32 this._fireAlert({
33 type: 'feeding',
34 severity: hoursSinceFeeding > 12 ? 'critical' : 'warning',
35 message: `${dino.name} hasn't been fed for ${formatTimeAgo(dino.lastFed)}`,
36 dinoId: dino.id
37 });
38 }
39
40 if (dino.behavior === 'escaped') {
41 this._fireAlert({
42 type: 'security',
43 severity: 'critical',
44 message: `BREACH: ${dino.name} (${dino.species}) has escaped!`,
45 dinoId: dino.id
46 });
47 }
48 }
49 } catch (error) {
50 console.error('Monitoring check failed:', error.message);
51 }
52 }
53
54 start(intervalMs = 30000) {
55 if (this.isRunning) return;
56 this.isRunning = true;
57 console.log('Park monitoring started');
58
59 const id = setInterval(() => {
60 this.checkDinosaurStatuses();
61 }, intervalMs);
62
63 this.intervals.push(id);
64 this.checkDinosaurStatuses(); // Run immediately
65 }
66
67 stop() {
68 this.intervals.forEach(id => clearInterval(id));
69 this.intervals = [];
70 this.isRunning = false;
71 console.log('Park monitoring stopped');
72 }
73}
74
75export const monitor = new ParkMonitor();7. Configuration (config.js)
Application configuration file:
1// data/config.js
2
3export const appConfig = {
4 // General application settings
5 appName: 'Jurassic Tracker',
6 version: '1.0.0',
7
8 // Monitoring settings
9 monitoring: {
10 dinoBehaviorInterval: 60000, // Dinosaur behavior monitoring interval (ms)
11 enclosureSecurityInterval: 90000, // Enclosure security monitoring interval (ms)
12 autoStart: true // Automatically start monitoring on startup
13 },
14
15 // API settings
16 api: {
17 baseUrl: 'https://jurassic-park-api.example.com', // In reality we'd use a real URL
18 timeout: 10000, // API request timeout (ms)
19 retryCount: 3 // Number of retry attempts for failed requests
20 },
21
22 // Dinosaur risk assessment
23 dinorisk: {
24 dangerousSpecies: ['Tyrannosaurus', 'Velociraptor', 'Spinosaurus', 'Allosaurus', 'Dilophosaurus'],
25 behaviorRiskMatrix: {
26 'calm': { base: 1, factor: 0.8 },
27 'grazing': { base: 1, factor: 0.9 },
28 'sleeping': { base: 2, factor: 0.7 },
29 'exploring': { base: 4, factor: 1.0 },
30 'agitated': { base: 6, factor: 1.2 },
31 'hunting': { base: 7, factor: 1.5 },
32 'aggressive': { base: 9, factor: 2.0 }
33 },
34 hungerRiskHours: 3, // Hours after which hunger starts affecting risk
35 hungerRiskFactor: 0.5 // Hunger impact factor on risk
36 },
37
38 // Security levels for notifications
39 security: {
40 alertThresholds: {
41 low: 3,
42 medium: 5,
43 high: 7,
44 critical: 9
45 },
46 autoResolveAlerts: false // Whether to automatically mark alerts as resolved
47 }
48};
49
50export default appConfig;8. Main Application File (index.js)
1// index.js
2import { getDinosaurs } from './modules/api.js';
3import { monitor } from './modules/monitoring.js';
4import { retry } from './modules/utils.js';
5
6async function initialize() {
7 console.log('=== JurassicTracker Starting ===');
8
9 // Set up alert handling
10 const unsubAlert = monitor.onAlert((alert) => {
11 const icon = alert.severity === 'critical' ? '!!' : '!';
12 console.log(`${icon} [${alert.type.toUpperCase()}] ${alert.message}`);
13 });
14
15 try {
16 // Fetch initial data with retry logic
17 const dinosaurs = await retry(() => getDinosaurs(), 3, 1000);
18 console.log(`Loaded ${dinosaurs.length} dinosaurs`);
19
20 // Start background monitoring
21 monitor.start(10000); // Check every 10 seconds
22
23 console.log('=== JurassicTracker Ready ===');
24
25 // Cleanup on shutdown
26 const cleanup = () => {
27 monitor.stop();
28 unsubAlert();
29 console.log('JurassicTracker shut down gracefully');
30 };
31
32 process.on('SIGINT', cleanup);
33 process.on('SIGTERM', cleanup);
34
35 } catch (error) {
36 console.error('Fatal initialization error:', error.message);
37 process.exit(1);
38 }
39}
40
41initialize();Summary of Applied Techniques
In our JurassicTracker application we used all the most important techniques learned in this module:
- ES Modules - the entire app is organized into modules that import and export functionality
- Asynchrony:
- We use async/await for clean asynchronous operation handling
- We use Promise.all for parallel operation execution
- We handle delays and time intervals
- Error handling:
- We created a hierarchy of custom error classes (APIError, DinoError, SecurityError)
- We use try/catch blocks for local error handling
- We implement retry logic for operations that can be retried
- Background monitoring:
- We use setInterval for cyclical monitoring tasks
- We implement a notification system based on the Observer pattern
- Application state management:
- We store state in module variables
- We use Map for efficient data storage
- We implement memoization for optimization
This application shows how to combine all the learned techniques into a functional, modular, and fault-tolerant system.
In the next exercise we'll delve into structuring code in even larger JavaScript projects, extending the techniques and patterns we've learned.
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 best practice for organizing modules in a JavaScript project?
Hands-on tasks in the game
- Code editor
Create a DinosaurDatabase with methods and a ParkMonitor with error handling.
- Vertical ordering
Order the project layers from lowest to highest: