Error Handling
Why Error Handling Matters
Every program encounters unexpected situations: a server goes down, a user enters invalid data, a file is missing. Without error handling, these situations cause your program to crash and display unhelpful technical messages to users.
Good error handling means your application fails gracefully — showing a friendly message, logging the problem, and recovering where possible.
Types of JavaScript Errors
JavaScript has several built-in error types:
| Error Type | When it occurs |
|---|---|
ReferenceError | Accessing a variable that does not exist |
TypeError | Calling a method on undefined or null |
SyntaxError | Invalid JavaScript syntax (caught at parse time) |
RangeError | A value is outside the allowed range |
URIError | Invalid use of URI (Uniform Resource Identifier) functions |
console.log(undeclaredVariable); // ReferenceError
null.toString(); // TypeError
eval("{"); // SyntaxError
new Array(-1); // RangeError
try / catch / finally
The try block runs code that might throw an error. If an error occurs, JavaScript jumps to the catch block. The finally block always runs, whether or not an error occurred:
try {
const data = JSON.parse("invalid json {");
console.log(data);
} catch (error) {
console.error("Parsing failed:", error.message);
} finally {
console.log("This always runs — good for cleanup");
}
The error object in the catch block has useful properties:
try {
null.toUpperCase();
} catch (error) {
console.log(error.name); // "TypeError"
console.log(error.message); // "Cannot read properties of null"
console.log(error.stack); // Full stack trace with line numbers
}
Throwing Custom Errors
You can throw your own errors using the throw statement. You can throw any value, but throwing an Error object is best practice because it includes a stack trace:
function divide(a, b) {
if (b === 0) {
throw new Error("Cannot divide by zero");
}
return a / b;
}
try {
console.log(divide(10, 2)); // 5
console.log(divide(10, 0)); // Throws!
} catch (error) {
console.error(error.message); // "Cannot divide by zero"
}
Custom Error Classes
For larger applications, create custom error types:
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = "ValidationError";
this.field = field;
}
}
function validateAge(age) {
if (typeof age !== "number") {
throw new ValidationError("Age must be a number", "age");
}
if (age < 0 || age > 150) {
throw new ValidationError("Age must be between 0 and 150", "age");
}
return true;
}
try {
validateAge("twenty");
} catch (error) {
if (error instanceof ValidationError) {
console.log(`Field: ${error.field}, Message: ${error.message}`);
} else {
throw error; // Re-throw unknown errors
}
}
Defensive Programming Patterns
Defensive programming anticipates problems before they occur:
// Guard clauses — check inputs at the top of a function
function processUser(user) {
if (!user) throw new Error("User is required");
if (!user.email) throw new Error("User email is required");
if (typeof user.age !== "number") throw new Error("User age must be a number");
// Safe to proceed
console.log("Processing:", user.email);
}
// Optional chaining — safely access nested properties
const user = null;
const city = user?.address?.city; // undefined, not an error
console.log(city); // undefined
// Nullish coalescing — provide a default
const displayName = user?.name ?? "Guest";
console.log(displayName); // "Guest"
Error Handling in Async Code
Always handle errors in async functions:
async function fetchWithErrorHandling(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
} catch (error) {
// Network errors and HTTP errors are both caught here
console.error("Request failed:", error.message);
return null; // Return a safe default
}
}
Key Takeaways
- JavaScript has built-in error types: ReferenceError, TypeError, SyntaxError, RangeError, and others
- try/catch/finally handles errors: try runs the code, catch handles errors, finally always runs
- The error object has name, message, and stack properties
- Use throw new Error("message") to raise your own errors with a descriptive message
- Create custom error classes by extending Error to add type-specific information
- Defensive programming with guard clauses, optional chaining (?.) and nullish coalescing (??) prevents many errors
Practice Exercise
- Write a function
parseJSON(str)that wrapsJSON.parsein a try/catch and returnsnullon failure with a console warning - Write a
createUser(name, email)function that throws a descriptive error if either argument is missing or not a string - Test both functions with valid and invalid inputs and verify the error messages
Try it yourself
Key Takeaways
- JavaScript has built-in error types including TypeError (method on null), ReferenceError (undeclared variable), and RangeError (value out of bounds)
- try/catch/finally provides structured error handling: try runs risky code, catch receives the error, finally always runs
- The error object has name, message, and stack properties useful for debugging
- throw new Error('message') raises a custom error; extend Error to create typed custom error classes
- Optional chaining (?.) prevents TypeErrors when accessing properties on potentially null values
- Defensive guard clauses at the start of functions validate inputs before proceeding
Quick Quiz
1.What is the purpose of the finally block in a try/catch/finally statement?
2.What type of error does null.toUpperCase() throw?
3.Why is it better to throw new Error('message') than throw 'message'?
4.What does the optional chaining operator (?.) do?
Ready to go further?
CareerEx gives you structured 12-week training, live classes every Saturday and Sunday, real tutor feedback, and a certificate. Join the next cohort.
Join CareerEx