Error Handling in JavaScript: Try, Catch, Finally

Error Handling in JavaScript: Try, Catch, Finally
Code breaks. Users do unexpected things. Networks fail. Without error handling, your app crashes. With it, you handle problems gracefully and stay in control.
Error handling isn't about preventing all mistakes. It's about deciding what to do when they happen.
What Errors Are in JavaScript
An error is when something goes wrong while code runs. JavaScript throws an error and stops.
Common Error Types
Reference Error — Using a variable that doesn't exist:
console.log(someVariable);
// ReferenceError: someVariable is not defined
Type Error — Calling a method on the wrong type:
let number = 42;
number.toUpperCase();
// TypeError: number.toUpperCase is not a function
Range Error — Value outside acceptable range:
new Array(-1);
// RangeError: Invalid array length
Without Error Handling
function loadFile(filename) {
const data = JSON.parse(fs.readFileSync(filename, "utf8"));
return data;
}
loadFile("missing.json");
// Error: file not found
// App crashes. User sees nothing.
With Error Handling
function loadFile(filename) {
try {
const data = JSON.parse(fs.readFileSync(filename, "utf8"));
return data;
} catch (error) {
return { default: true };
}
}
loadFile("missing.json");
// Returns: { default: true }
// App keeps running.
Using Try and Catch Blocks
The try block has code that might fail. The catch block handles it if it does.
Basic Structure
try {
// Code that might throw an error
riskyOperation();
} catch (error) {
// Handle the error
console.log("Something went wrong:", error.message);
}
Real Example: Parsing JSON
let jsonString = '{ "name": "Alice" }';
try {
let data = JSON.parse(jsonString);
console.log("Parsed successfully:", data);
} catch (error) {
console.log("Invalid JSON:", error.message);
}
If it's valid JSON, it parses. If not, catch handles it.
With Broken JSON
let jsonString = '{ "name": "Alice"'; // Missing closing brace
try {
let data = JSON.parse(jsonString);
console.log("Parsed:", data);
} catch (error) {
console.log("Invalid JSON:", error.message);
// Output: Invalid JSON: Unexpected end of JSON input
}
The error happens in try. Instead of crashing, catch handles it.
The Error Object
The error object has useful properties:
try {
nonexistentFunction();
} catch (error) {
console.log(error.message); // The message
console.log(error.name); // Error type
console.log(error.stack); // Full trace
}
Multiple Operations
try {
const data = fs.readFileSync("data.json", "utf8");
const parsed = JSON.parse(data);
processData(parsed);
} catch (error) {
console.log("Error:", error.message);
}
If any operation fails, catch handles it.
The Finally Block
The finally block always runs, no matter what. Success or failure, it executes.
Structure
try {
// Code that might fail
} catch (error) {
// Handle the error
} finally {
// Always runs
}
Real Example: Close a Database
const db = openDatabase("app.db");
try {
const user = db.query("SELECT * FROM users WHERE id = 1");
console.log("User found:", user);
} catch (error) {
console.log("Query failed:", error.message);
} finally {
db.close(); // Always close, success or failure
}
The database closes whether the query works or fails.
Execution Order
try block runs
|
├─ Success → skip catch → go to finally
└─ Failure → catch runs → then finally
finally always runs last
Resource Cleanup
function processFile(filename) {
let file;
try {
file = openFile(filename);
const content = file.read();
console.log("File processed");
} catch (error) {
console.log("Error:", error.message);
} finally {
if (file) {
file.close(); // Clean up
}
}
}
The file closes whether processing succeeds or fails.
Try → Catch → Finally Execution Flow
START
|
v
TRY BLOCK
|
├─── No error
| |
| v
| Code runs successfully
| |
| v
| SKIP catch block
| |
└──────┤
| |
├─── Error occurs
| |
| v
| Jump to CATCH block
| |
| v
| Handle error
| |
└──────┤
| |
v v
FINALLY BLOCK (always runs)
|
v
END
Throwing Custom Errors
When something is wrong with your logic, create your own error.
Throw Statement
function validateAge(age) {
if (age < 0) {
throw new Error("Age cannot be negative");
}
return age;
}
try {
validateAge(-5);
} catch (error) {
console.log("Validation failed:", error.message);
// Output: Validation failed: Age cannot be negative
}
Error Types You Can Throw
throw new Error("Something went wrong");
throw new TypeError("Expected a number");
throw new RangeError("Value out of range");
throw "Quick error message";
Custom Error Classes
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
}
}
function validateEmail(email) {
if (!email.includes("@")) {
throw new ValidationError("Invalid email");
}
return email;
}
try {
validateEmail("notanemail");
} catch (error) {
if (error instanceof ValidationError) {
console.log("Email error:", error.message);
}
}
When to Throw
function divideNumbers(a, b) {
if (typeof a !== "number" || typeof b !== "number") {
throw new TypeError("Both must be numbers");
}
if (b === 0) {
throw new Error("Cannot divide by zero");
}
return a / b;
}
try {
divideNumbers(10, 2); // Works: 5
divideNumbers(10, 0); // Throws error
} catch (error) {
console.log("Math error:", error.message);
}
Why Error Handling Matters
1. Graceful Failure
Without error handling, your app crashes:
// Crashes if database fails
app.get("/user/:id", (req, res) => {
const user = db.findUser(req.params.id);
res.json(user);
});
// Handles the error gracefully
app.get("/user/:id", (req, res) => {
try {
const user = db.findUser(req.params.id);
res.json(user);
} catch (error) {
res.status(500).json({ error: "User not found" });
}
});
User gets a message instead of a blank screen.
2. Better Debugging
Error objects show you where the problem is:
try {
riskyFunction();
} catch (error) {
console.log(error.stack);
// Shows file, line number, and function names
// Helps you find bugs fast
}
3. Resource Cleanup
With finally, resources always get cleaned up:
let connection;
try {
connection = openConnection();
} catch (error) {
console.log("Error:", error.message);
} finally {
if (connection) {
connection.close(); // Don't leak resources
}
}
Without finally, connections stay open if an error happens.
4. Better User Experience
Handle errors cleanly:
function saveData(data) {
try {
validateData(data);
db.save(data);
return { success: true };
} catch (error) {
return {
success: false,
message: "Could not save. Please try again."
};
}
}
Users see a clear message, not a crash.
Real-World Example: Parsing User Input
function processUserInput(jsonString) {
try {
// Parse the JSON
const data = JSON.parse(jsonString);
// Validate the data
if (!data.name || !data.email) {
throw new Error("Missing required fields");
}
// Process the data
const result = {
name: data.name.trim(),
email: data.email.toLowerCase()
};
console.log("Processing successful:", result);
return result;
} catch (error) {
if (error instanceof SyntaxError) {
console.log("Invalid JSON format:", error.message);
} else if (error.message.includes("required")) {
console.log("Validation error:", error.message);
} else {
console.log("Unexpected error:", error.message);
}
return null;
} finally {
console.log("Processing complete");
}
}
// Test it
processUserInput('{ "name": "Alice", "email": "alice@example.com" }');
// Output: Processing successful: { name: 'Alice', email: 'alice@example.com' }
// Processing complete
processUserInput('invalid json');
// Output: Invalid JSON format: Unexpected token i...
// Processing complete
processUserInput('{ "name": "Bob" }');
// Output: Validation error: Missing required fields
// Processing complete
Each scenario is handled appropriately.
Error Handling Patterns
Pattern 1: Try-Catch-Finally
try {
// Do something
} catch (error) {
// Handle error
} finally {
// Cleanup
}
Pattern 2: Catch and Rethrow
try {
database.query("SELECT * FROM users");
} catch (error) {
console.log("Database error:", error);
throw error; // Pass it up to the caller
}
Pattern 3: Catch and Return Default
function getConfig() {
try {
return loadConfig();
} catch (error) {
return defaultConfig; // Use defaults if load fails
}
}
Pattern 4: Catch Specific Errors
try {
operation();
} catch (error) {
if (error instanceof TypeError) {
console.log("Type error:", error.message);
} else if (error instanceof RangeError) {
console.log("Range error:", error.message);
} else {
console.log("Unknown error:", error.message);
}
}
Practice Assignment
1. Parse JSON safely:
const jsonString = '{ "name": "Alice", "age": 25 }';
// Write a function that parses the JSON
// If parsing fails, return a default object
2. Handle multiple errors:
function divideAndLog(a, b) {
try {
// Check if both are numbers
// Check if b is not zero
// Divide and log result
} catch (error) {
// Handle the error
}
}
3. Create a custom error:
// Create a PasswordError class
// Throw it if password is too short
// Catch it and show a message
4. Use finally for cleanup:
let file = null;
try {
file = openFile("data.txt");
// Read and process the file
} catch (error) {
// Handle error
} finally {
// Make sure file closes
}
Quick Recap
Errors happen when code breaks. JavaScript throws and stops.
Try block contains code that might fail.
Catch block handles errors if they happen.
Finally block always runs, success or failure.
Throw keyword creates your own errors.
Error objects have
.message,.stack, and.name.Error handling lets you fail gracefully instead of crashing.
Use
finallyto clean up resources.Catch specific error types for different handling.
Error messages help you debug faster.
Good error handling improves user experience.
Error handling isn't optional. It's how you control what happens when things go wrong.
Happy coding! 🚀
If you enjoyed this article, check out my other blogs on this profile. 🔗 Connect with me: LinkedIn | GitHub | X (Twitter)




