Errors are inevitable. Good code doesn’t just work when everything goes right — it stays predictable and safe when things go wrong.
In JavaScript, error handling is mainly done with three tools:
try...catch
blocks — to catch and handle exceptionsthrow
— to create and raise your own errors when something is invalid- Patterns to handle async errors, because JavaScript is heavily asynchronous
This article goes beyond the basics. We’ll cover:
- Why JavaScript errors happen
- How
try...catch
really works under the hood - The purpose of
throw
and when to use it - How to handle errors in promises and async/await
- Real-world design patterns: input validation, fallback values, logging, and user feedback
1. What is an error in JavaScript?
When the JavaScript engine encounters a problem it cannot resolve — like trying to access an undefined variable, calling a function that does not exist, or failing to parse data — it throws an error. If this error is not handled, it bubbles up and can crash your script.
// Uncaught ReferenceError
console.log(user.name);
// ReferenceError: user is not defined
The program stops here if you don’t catch this problem.
2. The Try-Catch Mechanism
Purpose: try...catch
lets you safely run code that might fail, and handle the failure in a controlled way.
How it works:
- Code inside the
try
block runs normally. - If an error is thrown inside
try
, JavaScript stops running the rest oftry
immediately. - Control jumps to the
catch
block. - The
catch
block gets the error object with information about what went wrong.
Basic syntax:
try {
// code that might fail
} catch (error) {
// code to handle the failure
}
If no error occurs in try
, the catch
block is skipped entirely.
Example: Parsing JSON that might be malformed
try {
const jsonString = '{"name":"Alice"}';
const user = JSON.parse(jsonString);
console.log(user.name); // Alice
// This JSON is invalid: missing quote
const badJson = '{"name": Alice}';
JSON.parse(badJson);
} catch (err) {
console.error("JSON parsing failed:", err.message);
}
Use try...catch
only around risky operations: user input parsing, network requests, and file operations.
3. The throw
Statement — Creating Your Own Errors
Sometimes, your program detects a problem that the JavaScript engine itself would not consider an error. For example, maybe a number is negative when it should not be.
To handle this, you can throw your own errors.
Basic syntax:
throw new Error("Something went wrong");
When you throw
:
- Execution immediately stops at the throw.
- Control looks for the nearest
catch
block up the call stack. - If no
catch
exists, the program crashes.
Example: Validating a function argument
function calculateArea(radius) {
if (radius <= 0) {
throw new Error("Radius must be positive");
}
return Math.PI * radius * radius;
}
try {
console.log(calculateArea(5)); // Works fine
console.log(calculateArea(-2)); // Throws
} catch (err) {
console.error("Calculation failed:", err.message);
}
Use throw
when you hit a state that should never happen in correct usage. It enforces contracts: "This function must not get bad input."
4. The finally
Block — Guaranteed Cleanup
finally
always runs, whether the code in try
succeeds, fails, or even if you return
from try
.
Example:
try {
console.log("Opening connection");
throw new Error("Connection failed");
} catch (err) {
console.error("Error:", err.message);
} finally {
console.log("Closing connection");
}
// Output:
// Opening connection
// Error: Connection failed
// Closing connection
Use finally
for closing files or database connections, stopping loaders/spinners, or resetting states.
5. Asynchronous Code: The Common Trap
JavaScript runs lots of code asynchronously — setTimeout, fetch, promises. try...catch
does not automatically catch errors that happen inside callbacks or promises.
Example:
try {
setTimeout(() => {
throw new Error("Oops");
}, 1000);
} catch (err) {
console.log("Caught:", err.message); // Never runs
}
How to Handle Async Errors Properly
Wrap inside the async function
setTimeout(() => {
try {
throw new Error("Oops inside timeout");
} catch (err) {
console.error("Caught:", err.message);
}
}, 1000);
Promises: use .catch
fetch("https://bad.url")
.then(res => res.json())
.catch(err => console.error("Network or parsing failed:", err.message));
Async/await: wrap with try-catch
async function fetchUser() {
try {
const response = await fetch("https://bad.url");
const data = await response.json();
console.log(data);
} catch (err) {
console.error("Async/await failed:", err.message);
}
}
fetchUser();
6. Real-World Example: Form Validation
Putting it together with a user registration check.
function registerUser(user) {
if (!user.username) {
throw new Error("Username is required");
}
if (user.password.length < 8) {
throw new Error("Password must be at least 8 characters");
}
return "User registered!";
}
try {
const user = { username: "John", password: "123" };
const result = registerUser(user);
console.log(result);
} catch (err) {
console.error("Registration failed:", err.message);
}
7. Logging and Rethrowing
Catch an error just to log it, then rethrow so a higher-level handler can deal with it.
function processData(data) {
try {
if (!data) {
throw new Error("No data");
}
// process...
} catch (err) {
console.error("Log:", err.message);
throw err; // propagate further
}
}
try {
processData(null);
} catch (err) {
console.log("Final catch:", err.message);
}
8. Best Practices
- Never ignore errors silently.
- Add meaningful, precise messages.
- Use custom error classes for clarity if needed.
- Catch only what you can handle. Don’t catch and swallow everything.
- For async operations, always
.catch()
promises or usetry-catch
withawait
. - Always log unexpected errors somewhere.
Conclusion
Bad things happen in software — it’s how you prepare for them that separates a robust application from a fragile one. Handle predictable failures, fail loudly on developer errors, and never let unexpected problems silently break your users’ trust.
If you understand how try...catch
, throw
, and async error handling fit together, you have a safety net for whatever the real world throws at your code.