JavaScript Error Handling Explained — Try/Catch, Error Types & Global Handling
JavaScript error handling is the practice of catching, managing, and recovering from runtime errors so your application doesn’t crash unexpectedly. Every real-world app — from the Doda Browser to Durga Antivirus Pro — relies on structured error handling to stay stable when things go wrong.
What You’ll Learn
- The
try/catch/finallypattern and how it works - Built-in Error types — SyntaxError, ReferenceError, TypeError, RangeError
- Creating custom error classes with proper stack traces
- Global error handlers —
window.onerrorandwindow.onunhandledrejection - Error handling patterns for async code (callbacks, promises, async/await)
Why Error Handling Matters
Without error handling, a single failed API call or a null reference can crash your entire app. In the Durga Antivirus Pro dashboard, one unhandled error during a threat scan could freeze the UI and prevent the user from seeing critical alerts. Proper error handling keeps your app running gracefully, logs what went wrong, and often lets the user continue without even noticing there was a problem.
Learning Path
flowchart LR
A[Async JavaScript] --> B[Error Handling]
B --> C[Fetch API & HTTP]
B --> D[Closures & Scope]
B --> E[You Are Here]
The try/catch/finally Pattern
The core of JavaScript error handling is the try/catch/finally block. Think of it like a safety net: you try something risky, catch the error if it fails, and run cleanup code either way.
try {
// Code that might throw
const data = JSON.parse('{"invalid": broken}');
console.log('Parsed:', data);
} catch (error) {
// Runs only if try block throws
console.error('Parse failed:', error.message);
// Output: Parse failed: Unexpected token b in JSON at position 14
} finally {
// Always runs — even if catch re-throws
console.log('Cleanup complete');
// Output: Cleanup complete
}Line by line:
try— Wrap code that might throw an error. If everything goes well,catchis skipped.catch(error)— Catches any error thrown in thetryblock. Theerrorobject hasname,message, andstackproperties.finally— Always runs, whether there was an error or not. Use it for closing files, hiding spinners, or cleaning up.
Anticipating confusion: You don’t need catch and finally together. You can use try/finally (when you want cleanup but don’t care about the error) or try/catch (when you want to handle errors but don’t need cleanup).
Built-in Error Types
JavaScript provides several built-in error constructors, each for a specific kind of failure:
// ReferenceError — accessing undefined variable
console.log(nonExistent);
// Uncaught ReferenceError: nonExistent is not defined
// TypeError — calling something that's not a function
const num = 42;
num(); // Uncaught TypeError: num is not a function
// SyntaxError — invalid language syntax
eval('hello world');
// Uncaught SyntaxError: Unexpected identifier
// RangeError — value out of allowed range
new Array(-1);
// Uncaught RangeError: Invalid array length
When each type fires:
ReferenceError— You tried to use a variable that doesn’t exist or is in the temporal dead zone.TypeError— You performed an operation on a value of the wrong type (like calling a number).SyntaxError— The JavaScript parser found invalid syntax. Only happens ineval()or parsing code at runtime.RangeError— A numeric value is outside its allowed range.URIError—decodeURI()orencodeURI()received malformed input.
Custom Error Classes
For real applications, you want specific error types — not just generic Error objects. Extend the Error class to create your own:
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = 'ValidationError';
this.field = field;
}
}
class NetworkError extends Error {
constructor(message, statusCode) {
super(message);
this.name = 'NetworkError';
this.statusCode = statusCode;
}
}
try {
throw new ValidationError('Email is required', 'email');
} catch (error) {
if (error instanceof ValidationError) {
console.log(`Field: ${error.field}`); // 'email'
console.log(error.message); // 'Email is required'
}
}Why custom errors matter: In a large app like Doda Browser, different parts of the code need to handle errors differently. A NetworkError might trigger a retry, while a ValidationError should show a message to the user. Custom error classes let you distinguish between these cases with instanceof.
The throw Statement
You can throw any value — but always throw an Error object for proper stack traces:
function divide(a, b) {
if (b === 0) {
throw new Error('Division by zero');
}
if (typeof a !== 'number' || typeof b !== 'number') {
throw new TypeError('Both arguments must be numbers');
}
return a / b;
}
try {
divide(10, 0);
} catch (err) {
console.log(err.message); // 'Division by zero'
console.log(err.stack); // Full stack trace
}Rule of thumb: Always throw new Error(...), never throw "string". Error objects give you stack traces that tell you exactly where the error happened.
Global Error Handling
Errors that aren’t caught anywhere bubble up to the global scope. Use these handlers as a last resort:
// Catch all uncaught errors
window.onerror = function(message, source, line, column, error) {
console.error('Global error:', message);
// Log to your error reporting service
return true; // Prevents the default browser error handling
};
// Catch unhandled promise rejections
window.onunhandledrejection = function(event) {
console.error('Unhandled rejection:', event.reason);
event.preventDefault();
};In Node.js, the equivalents are process.on('uncaughtException') and process.on('unhandledRejection').
Async Error Handling
Async code needs special care. A thrown error inside a setTimeout or promise chain won’t be caught by an outer try/catch:
// ❌ This won't catch the error
try {
setTimeout(() => {
throw new Error('Async error');
}, 1000);
} catch (err) {
console.log('Caught?'); // Never runs
}
// ✅ Catch inside the async callback
setTimeout(() => {
try {
throw new Error('Async error');
} catch (err) {
console.log('Caught!'); // Runs
}
}, 1000);Fetch with Error Handling
A complete, real-world pattern for handling fetch errors:
async function fetchUserData(userId) {
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
if (!response.ok) {
throw new NetworkError(
`Request failed with status ${response.status}`,
response.status
);
}
const data = await response.json();
if (!data.id) {
throw new ValidationError('Invalid user data received', 'userId');
}
return data;
} catch (error) {
if (error instanceof NetworkError) {
// Retry logic or show offline message
console.warn('Network issue, will retry...');
throw error; // Re-throw if caller needs to know
}
if (error instanceof ValidationError) {
console.error('Bad data from server:', error.message);
return null; // Graceful degradation
}
// Unknown error — log and re-throw
console.error('Unexpected error:', error);
throw error;
}
}Why this pattern works: Each error type gets its own handling strategy. Network errors trigger retries. Validation errors return null gracefully. Unknown errors bubble up to the global handler.
Common Mistakes
1. Swallowing errors silently
try {
riskyOperation();
} catch (err) {
// Do nothing — error is lost forever!
}Fix: At minimum log the error. Never leave empty catch blocks.
2. Catching without re-throwing when you can’t handle it
try {
// Some operation
} catch (err) {
console.log('Error happened'); // But you can't actually recover
}Fix: Re-throw errors you can’t handle: throw err;
3. Not distinguishing error types
catch (err) {
// Same handling for ReferenceError and TypeError
}Fix: Use instanceof checks to handle different errors differently.
4. Throwing non-Error values
throw 'Something went wrong'; // No stack trace!
Fix: Always throw new Error('message').
5. Forgetting try/catch in async functions
async function load() {
const data = await fetch('/api/data'); // If this fails, it's unhandled
}Fix: Wrap in try/catch or add .catch() to the returned promise.
Practice Questions
What’s the difference between
throw new Error()andthrow "string"?new Error()creates an object withname,message, andstackproperties. Throwing a string gives you no stack trace, making debugging much harder.Does
finallyalways run? Yes — even if thecatchblock re-throws, or thetryblock has areturnstatement.finallyruns before control leaves the try/catch/finally structure.Why can’t an outer try/catch catch an error inside
setTimeout? ThesetTimeoutcallback runs later, after the try/catch block has already finished. The error is thrown in a new execution context.What is
window.onerrorused for? It’s a global error handler that catches any uncaught error in the window. Use it as a last resort for logging errors to your monitoring service.
Challenge: Create a retry(fn, maxAttempts, delay) function that retries an async operation up to maxAttempts times with a delay between attempts. Use custom error types to distinguish between retryable and non-retryable errors.
Mini Project: Safe JSON Parser
Build a function that parses JSON safely and reports structured errors:
class ParseResult {
constructor(data, error) {
this.data = data;
this.error = error;
}
get success() { return this.error === null; }
}
function safeParse(jsonString) {
try {
const data = JSON.parse(jsonString);
return new ParseResult(data, null);
} catch (error) {
if (error instanceof SyntaxError) {
return new ParseResult(null, {
type: 'invalid_json',
message: error.message,
position: error.message.match(/position (\d+)/)?.[1]
});
}
return new ParseResult(null, {
type: 'unknown',
message: error.message
});
}
}
const result = safeParse('{"name": "Alice"}');
console.log(result.success); // true
console.log(result.data); // { name: 'Alice' }
FAQ
What’s Next
| Lesson | Description |
|---|---|
| JavaScript Home | Back to the JavaScript hub |
| https://tutorials.dodatech.com/programming-languages/javascript/js-async/ | Async JavaScript — promises, async/await |
| https://tutorials.dodatech.com/programming-languages/javascript/js-modules/ | Modules & dynamic imports |
| https://tutorials.dodatech.com/programming-languages/javascript/js-fetch-api/ | Fetch API — HTTP requests |
| Node.js | Error handling in Node.js |
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
What’s Next
Congratulations on completing this JavaScript Error Handling tutorial! Here’s where to go from here:
- Practice daily — Consistency is more important than long study sessions
- Build a project — Apply what you learned by building something real
- Explore related topics — Check out other tutorials in the same category
- Join the community — Discuss with other learners and share your progress
Remember: every expert was once a beginner. Keep coding!
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro