Learn how to handle errors in JavaScript using try...catch. Understand throwing errors, the finally block, common mistakes, and best practices with examples.
Loading...
Error Handling: try...catch
While coding, unexpected things can happen that break your program. That unexpected thing is an error.
Errors can occur when:
You call a function that doesn’t exist
You try to parse malformed JSON
A network request fails
You access properties of undefined or null
JavaScript throws different types of errors, like:
ReferenceError: Using a variable that doesn’t exist
TypeError: Doing something invalid with a value (like calling something that’s not a function)
SyntaxError: Mistyped code that can’t be parsed
RangeError: Using values outside valid limits (e.g., new Array(-1))
If you don't handle these errors, your program will crash.
Thankfully, JavaScript gives us try...catch, a structured way to deal with these errors, so your app can fail gracefully instead of exploding.
Basic try...catch Syntax
try { // Code that might throw an error} catch (error) { // Code that runs if there’s an error}
Example:
try { const data = JSON.parse('{"name": "Shefali"}'); console.log(data.name);} catch (error) { console.log("Something went wrong:", error.message);}// Output: Shefali
This runs perfectly. But what if I mess this code?
try { const data = JSON.parse('{"name" "Shefali"}'); console.log(data.name);} catch (error) { console.log("Something went wrong:", error.message);}// Output: Something went wrong: JSON.parse: expected ':' after property name in object
Here, I removed : in the JSON, so it will jump to the catch block and will not crash the app.
Without the catch block, your app would crash.
Throwing Your Own Errors
You can create and throw your custom errors using throw.
throw new Error("Something broke!");
Example:
function divide(a, b) { if (b === 0) { throw new Error("Can't divide by zero!"); } return a / b;}try { console.log(divide(10, 0));} catch (err) { console.log("Error:", err.message); // Error: Can't divide by zero!}
Here,
divide(a, b) is a function.
It takes two numbers, a (numerator) and b (denominator), and returns a / b.
It checks if b is zero.
Division by zero isn’t allowed in math, it would return Infinity or break things.
If b is 0, it throws an error throw new Error("Can't divide by zero!"); and immediately stops execution inside the function.
try...catch handles the error:
The function call divide(10, 0) goes inside try.
Since b is 0, it throws an error.
The catch block catches the error and logs its message.
Output: Error: Can't divide by zero!.
Use this when:
Inputs are invalid
Conditions are unexpected
You want to stop and handle issues early
This is helpful because, it:
Prevents crashes due to invalid inputs
Gives clear, helpful error messages
Keeps logic clean and predictable
Why Use try...catch?
Prevent app crashes
Handle unexpected issues gracefully
Show friendly error messages
Retry operations
Log bugs
Without try...catch, errors can crash the whole program:
const data = JSON.parse("not-valid-json");console.log("You won't reach this line.");
This throws a SyntaxError and stops further execution.
But using try...catch you can find out what's causing the error.
Inside catch, you can get the info about the error object.
async function fetchData() declares an async function, meaning it will use await to handle asynchronous code like API calls.
Inside try block:
await fetch(...): Sends a network request to the given API URL.
await res.json(): Waits for the response and parses the JSON body.
return data: If everything works, the parsed data is returned.
If anything fails (network error, bad response, etc.), catch (err) runs.
Logs the error message: console.log("API failed:", err.message);.
Returns a fallback object: return { fallback: true };.
Example output:
If API works, it returns the actual data.
If API fails, it returns { fallback: true }.
Common Mistakes
Don’t catch everything silently. Always log or handle errors, or you'll hide bugs.
try { // some code} catch (e) { // don't just ignore it}
Don't wrap too much code inside try...catch. It makes it hard to know which line caused the error. Try to keep only the risky code inside the try block.
// Not ideal: Tries to catch everything at once, making it hard to know what failed.try {// fetch// process data// update UI} catch (e) {console.log("Something failed");}// Better: Catches only the risky part, so errors are easier to trace and handle.try {const res = await fetch(...);const data = await res.json();} catch (e) {console.log("API fetch failed:", e.message);}
Don't use try...catch for things you can check with if. It’s meant for unexpected errors, not for simple checks you can do ahead of time.
// Don't do thistry { if (!input) throw new Error("No input");} catch (e) { console.log(e.message);}// Do thisif (!input) { console.log("No input");}
Don’t forget that await only works inside async functions. If you use await without async, or forget to handle errors, it won’t work and will throw a SyntaxError.
// This won't catch the errortry { const data = await fetch("...");} catch (e) { console.log("Error"); // SyntaxError: await is only valid in async functions, async generators and modules}
Best Practices for try...catch
Use try...catch only when necessary. Too much error handling can slow things down.
Keep try blocks small. Only include the risky code, not unrelated logic.
Use specific error messages to make errors meaningful for debugging and users.
Avoid empty catch blocks. At least log the error to console.error().
Use finally for cleanup. Even if nothing goes wrong, make sure things close properly.
Throw early, catch late. Throw errors as soon as something looks wrong. Catch them later where you can handle them properly.