In Jurassic Park, things go wrong - fences fail, sensors malfunction, dinosaurs escape. The safety protocols don't panic when problems arise - they follow structured procedures: detect the problem, categorize it, respond appropriately, and document what happened. JavaScript's error handling works exactly the same way.
The fundamental error handling structure:
1function checkFence(zoneId) {
2 try {
3 // Code that might throw an error
4 if (!zoneId) {
5 throw new Error('Zone ID is required');
6 }
7
8 const status = getFenceStatus(zoneId); // may throw
9 console.log(`Fence OK: ${status}`);
10 return status;
11
12 } catch (error) {
13 // Handle the error
14 console.error(`Fence check failed: ${error.message}`);
15 return 'UNKNOWN';
16
17 } finally {
18 // Always executes - cleanup code
19 console.log('Fence check procedure complete');
20 }
21}JavaScript has several built-in error types:
1// TypeError - wrong type
2try {
3 null.property; // cannot read property of null
4} catch (e) {
5 console.log(e instanceof TypeError); // true
6 console.log(e.message); // "Cannot read properties of null"
7}
8
9// RangeError - value out of valid range
10try {
11 new Array(-1); // invalid array length
12} catch (e) {
13 console.log(e instanceof RangeError); // true
14}
15
16// ReferenceError - using undefined variable
17try {
18 console.log(undeclaredVar);
19} catch (e) {
20 console.log(e instanceof ReferenceError); // true
21}
22
23// SyntaxError - rare at runtime (usually compile-time)
24try {
25 eval('function('); // invalid syntax
26} catch (e) {
27 console.log(e instanceof SyntaxError); // true
28}You can throw any value, but it's best practice to throw
Error objects:1function validateDinoData(data) {
2 if (!data.name) {
3 throw new TypeError('Dinosaur name is required');
4 }
5
6 if (data.weight <= 0) {
7 throw new RangeError(`Weight must be positive, got ${data.weight}`);
8 }
9
10 if (!['carnivore', 'herbivore', 'omnivore'].includes(data.diet)) {
11 throw new Error(`Unknown diet type: ${data.diet}`);
12 }
13
14 return true;
15}
16
17try {
18 validateDinoData({ name: 'Rex', weight: -100, diet: 'carnivore' });
19} catch (e) {
20 if (e instanceof RangeError) {
21 console.log('Invalid range:', e.message);
22 } else if (e instanceof TypeError) {
23 console.log('Type error:', e.message);
24 } else {
25 console.log('Unknown error:', e.message);
26 }
27}Create your own error types for domain-specific errors:
1class ParkError extends Error {
2 constructor(message, code) {
3 super(message);
4 this.name = 'ParkError';
5 this.code = code;
6 }
7}
8
9class FenceBreachError extends ParkError {
10 constructor(zone, dinoName) {
11 super(`Fence breach in zone ${zone} - ${dinoName} escaped!`, 'FENCE_BREACH');
12 this.name = 'FenceBreachError';
13 this.zone = zone;
14 this.dinoName = dinoName;
15 }
16}
17
18class DinoHealthCriticalError extends ParkError {
19 constructor(dinoName, health) {
20 super(`${dinoName} health critical: ${health}%`, 'HEALTH_CRITICAL');
21 this.name = 'DinoHealthCriticalError';
22 this.dinoName = dinoName;
23 this.health = health;
24 }
25}
26
27try {
28 throw new FenceBreachError('A', 'Rex');
29} catch (e) {
30 if (e instanceof FenceBreachError) {
31 console.log(`BREACH! Zone: ${e.zone}, Dinosaur: ${e.dinoName}`);
32 console.log(`Code: ${e.code}`);
33 } else if (e instanceof ParkError) {
34 console.log(`Park error: ${e.message}`);
35 } else {
36 throw e; // re-throw unknown errors!
37 }
38}?. safely accesses nested properties - returns undefined instead of throwing:1const park = {
2 zones: {
3 A: {
4 dinos: [{ name: 'Rex', health: 95 }]
5 }
6 }
7};
8
9// Without optional chaining - might throw
10// const health = park.zones.B.dinos[0].health; // TypeError!
11
12// With optional chaining - safe
13const healthA = park.zones?.A?.dinos?.[0]?.health;
14console.log(healthA); // 95
15
16const healthB = park.zones?.B?.dinos?.[0]?.health;
17console.log(healthB); // undefined - no error!
18
19// With method calls
20const status = park.zones?.A?.getStatus?.();
21console.log(status); // undefined (method doesn't exist - no error)?? returns the right side only when the left is null or undefined (not 0, false, or `''``):1const dinoHealth = null;
2const defaultHealth = 100;
3
4// ?? vs ||
5console.log(dinoHealth ?? defaultHealth); // 100 (null → use default)
6console.log(0 ?? defaultHealth); // 0 (0 is not null/undefined!)
7console.log(0 || defaultHealth); // 100 (0 is falsy with ||)
8
9const config = {
10 voltage: 0, // explicitly 0
11 active: false, // explicitly false
12 name: '' // empty string
13};
14
15// ?? respects explicit false-y values
16console.log(config.voltage ?? 10000); // 0 (explicit 0 respected)
17console.log(config.active ?? true); // false (explicit false respected)
18console.log(config.name ?? 'unnamed'); // '' (explicit empty string respected)
19console.log(config.missing ?? 'N/A'); // 'N/A' (undefined → use default)1function getDinoHealth(park, zoneName, dinoIndex) {
2 try {
3 const health = park?.zones?.[zoneName]?.dinos?.[dinoIndex]?.health ?? 'unknown';
4 return health;
5 } catch (e) {
6 console.error(`Error getting health: ${e.message}`);
7 return 'error';
8 }
9}
10
11// Works safely even with missing data
12console.log(getDinoHealth(park, 'A', 0)); // 95
13console.log(getDinoHealth(park, 'B', 0)); // "unknown"
14console.log(getDinoHealth(null, 'A', 0)); // "unknown""Error handling is like the park's emergency response protocol" - says Dr. Rex. "You don't panic when an alarm sounds - you check the type of alarm, follow the procedure, document everything. try/catch is that structured response, and custom errors are the specific alarm types!"