JavaScript Error Handling

In the previous lesson, we learned how to request data from a server with the Fetch API. Now let’s learn how to handle errors gracefully, so a single failure does not crash the whole program.

πŸ›‘οΈ What is Error Handling?

An error is a problem that stops your code from running normally. Without a plan, one error halts everything that comes after it. Error handling lets you catch that problem, deal with it, and keep going. The tool for this is the try...catch statement.

error-handling.js
try {
// code that might fail
} catch (error) {
// code that runs only if something fails
}

The code in try runs first. If it fails, JavaScript jumps straight to catch instead of stopping the program.

🧩 try, catch, and finally

A full error-handling block has three parts. Each one has a clear job.

Block When it runs Its job
try Always, first Holds the code that might fail
catch Only if try fails Handles the error and receives the error object
finally Always, last Runs cleanup whether or not there was an error

The finally block is optional, but it is handy for cleanup steps that must happen either way, like hiding a loading spinner.

error-handling.js
try {
console.log("Trying...");
} catch (error) {
console.log("Something failed:", error.message);
} finally {
console.log("This always runs.");
}

Let’s walk through what each block does here:

  • The try block runs first and logs "Trying...". Since nothing fails, JavaScript skips the catch block.
  • The catch block stays idle because there was no error; it would only run if the try code had failed.
  • The finally block runs at the end no matter what, so "This always runs." is logged whether the code succeeded or failed.

πŸ“¦ A Simple Example

Some code is risky and can fail at runtime. A good example is JSON.parse, which turns a string into an object but throws an error if the string is not valid JSON.

error-handling.js
const badData = "{ not valid json }";
try {
const result = JSON.parse(badData);
console.log(result);
} catch (error) {
console.log("Could not read the data:", error.message);
}

Here JSON.parse fails on the broken string. Instead of crashing, JavaScript jumps to catch, where the error object tells you what went wrong through its message property. The program then continues normally.

The error object

The value in catch (error) is an error object. Its error.message property is a human-readable description of the problem, which is the most useful piece of information for figuring out what failed.

🚨 Throwing Your Own Errors

You can also create an error on purpose with throw new Error("message"). This is useful when something is technically valid JavaScript but wrong for your program, like a missing value or a bad input.

error-handling.js
function getDiscount(price) {
if (price < 0) {
throw new Error("Price cannot be negative");
}
return price * 0.9;
}
try {
console.log(getDiscount(-50));
} catch (error) {
console.log("Error:", error.message); // Error: Price cannot be negative
}

When throw runs, it stops the function immediately and sends the error to the nearest catch block, just like a built-in error would.

🌐 Error Handling with async/await

In the previous lesson we used fetch with async/await. A network request can fail for many reasons, so wrapping it in try...catch is the standard way to handle those failures. Anything that goes wrong inside try lands in catch.

error-handling.js
async function loadUser() {
try {
const response = await fetch("https://api.example.com/user");
if (!response.ok) {
throw new Error("Request failed with status " + response.status);
}
const data = await response.json();
console.log(data);
} catch (error) {
console.log("Could not load user:", error.message);
}
}
loadUser();

A failed network call makes await fetch reject, which sends the error to catch. Note the extra check on response.ok: fetch does not treat a 404 or 500 as a failure on its own, so we throw our own error to handle those cases too.

fetch and error status codes

fetch only rejects on network problems, not on HTTP errors like 404 or 500. Always check response.ok and throw your own error so bad responses reach your catch block.

πŸ™‚ Showing a Friendly Message

Users should never see a raw crash. With try...catch, you can catch the failure and show a clear, friendly message instead.

error-handling.js
async function showPrice() {
const status = document.querySelector("#status");
try {
const response = await fetch("https://api.example.com/price");
if (!response.ok) {
throw new Error("Bad response");
}
const data = await response.json();
status.textContent = "The price is " + data.price;
} catch (error) {
status.textContent = "Sorry, we could not load the price right now.";
console.log("Details:", error.message);
}
}

The user sees a calm, helpful sentence on screen, while the technical detail is logged to the console for you. The app keeps working instead of breaking.

⚠️ Common Mistakes to Avoid

Mistake Problem Solution
An empty catch block The error is hidden, so bugs become invisible At least log error.message so you can see it
Wrapping too much code in one try You cannot tell which line actually failed Wrap only the risky lines that can fail
Not handling a rejected promise An await failure crashes with an unhandled rejection Put await calls inside try...catch

πŸ”§ Try It Yourself!

  1. Wrap a JSON.parse call around a broken string in try...catch and log error.message.
  2. Write a function that uses throw new Error(...) when its input is invalid, then call it inside try...catch.
  3. Add a finally block that prints a message and confirm it runs whether or not there was an error.
  4. Wrap an await fetch call in try...catch and show a friendly message when it fails.

🧩 What You’ve Learned

  • βœ… try...catch handles errors gracefully without crashing the program
  • βœ… The catch block receives an error object, and error.message describes the problem
  • βœ… finally always runs, which makes it perfect for cleanup
  • βœ… throw new Error("message") creates your own errors on purpose
  • βœ… Wrapping await fetch in try...catch is the standard way to handle failed requests

πŸš€ What’s Next?

Now that you can handle errors safely, let’s learn how to save data in the browser. Let’s continue to Local Storage.

Share & Connect