Async/Await Error Handling in JavaScript
Async/Await makes asynchronous code easier to write and understand, but errors can still happen. In this article, you will learn how to handle errors in a simple and clean way using try...catch, response checks, fallback values, and reusable helper functions.
“Async/Await makes code cleaner, but strong error handling makes applications reliable.”
What is Async/Await?
Async/Await is a modern way to handle asynchronous operations in JavaScript. It helps you write code that looks simple and readable, even when your program is waiting for data from an API, server, or database.
For example, when you fetch user data from an API, the result does not come instantly. JavaScript continues running other tasks while waiting for the data.
But if something goes wrong during that process, such as a network problem or invalid response, you need proper error handling.
Why Error Handling is Important
- It prevents your application from breaking unexpectedly.
- It helps you show better messages to users.
- It makes debugging easier for developers.
- It allows you to return fallback data if the main request fails.
- It improves the stability and quality of your app.
1. Use try...catch for Basic Error Handling
The easiest and most common way to handle errors in Async/Await is by using try...catch.
async function fetchUser() {
try {
const response = await fetch("https://api.example.com/user");
const data = await response.json();
console.log(data);
} catch (error) {
console.error("Something went wrong:", error);
}
}
How it works
Code inside the try block runs normally. If an error happens, JavaScript jumps to the catch block.
When to use it
Use it for API calls, async tasks, database requests, or any operation that may fail.
2. Check response.ok After Fetch
Many beginners think that fetch() automatically throws an error for bad HTTP responses
like 404 or 500. But that does not always happen.
That is why you should manually check response.ok.
async function getPosts() {
try {
const response = await fetch("https://api.example.com/posts");
if (!response.ok) {
throw new Error("Failed to fetch posts");
}
const posts = await response.json();
console.log(posts);
} catch (error) {
console.error(error.message);
}
}
Important: A failed HTTP status does not always mean fetch will throw an error automatically.
So always check response.ok.
3. Handle Specific Errors Clearly
Sometimes you want to show different messages for different problems. For example, a 404 error and a 500 error should not always have the same message.
async function loadProfile() {
try {
const response = await fetch("https://api.example.com/profile");
if (response.status === 404) {
throw new Error("Profile not found");
}
if (response.status === 500) {
throw new Error("Server error");
}
const data = await response.json();
console.log(data);
} catch (error) {
console.error("Error:", error.message);
}
}
This makes your code easier to debug and gives users better feedback.
4. Use Fallback Values
If an API fails, you do not always want the full app to stop working. In many cases, returning a fallback value is a better option.
async function getProducts() {
try {
const response = await fetch("https://api.example.com/products");
if (!response.ok) {
throw new Error("Unable to load products");
}
return await response.json();
} catch (error) {
console.error(error.message);
return [];
}
}
In this example, if the API request fails, the function returns an empty array instead of crashing.
5. Use finally for Cleanup Work
The finally block runs whether the code succeeds or fails.
It is useful for cleanup tasks like hiding a loading spinner or stopping a progress bar.
async function fetchData() {
try {
console.log("Loading started");
const response = await fetch("https://api.example.com/data");
const data = await response.json();
console.log(data);
} catch (error) {
console.error("Error:", error.message);
} finally {
console.log("Loading finished");
}
}
This helps maintain a good user experience, especially in frontend applications.
6. Create Reusable Helper Functions
If you write the same error handling logic in many places, your code becomes repetitive. A better approach is to create a reusable helper function.
async function fetchJson(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error("Request failed");
}
return await response.json();
} catch (error) {
console.error("Fetch error:", error.message);
return null;
}
}
async function showUsers() {
const users = await fetchJson("https://api.example.com/users");
console.log(users);
}
This makes your code cleaner, shorter, and easier to maintain.
7. Throw Custom Errors When Needed
Sometimes the request works, but your business logic still needs validation. In that case, you can throw your own custom error.
async function getUserAge() {
try {
const response = await fetch("https://api.example.com/user-age");
const data = await response.json();
if (data.age < 18) {
throw new Error("User is under 18");
}
console.log("Access allowed");
} catch (error) {
console.error(error.message);
}
}
This is helpful when the API response is valid, but your app rules are not satisfied.
Common Mistakes to Avoid
1. Forgetting try...catch
If you use await without proper handling, unhandled errors can stop the function.
2. Not checking response.ok
A bad response from fetch may not throw automatically, so always check it manually.
3. Ignoring error messages
Do not catch errors and do nothing. Always log or handle them properly.
4. Showing raw technical errors to users
Keep technical details for developers and show user-friendly messages on the screen.
Best Practices for Async/Await Error Handling
- Always use try...catch when working with await.
- Check response.ok for fetch requests.
- Use clear and simple error messages.
- Return fallback values when useful.
- Create reusable helpers for repeated async logic.
- Use finally for cleanup tasks.
- Show clean UI messages instead of raw system errors.
Conclusion
Async/Await makes JavaScript code simple and readable, but errors must still be handled properly. By using try...catch, checking API responses, using fallback values, and writing reusable helper functions, you can build applications that are more stable and professional.
Good error handling is not just about fixing problems. It is also about creating a better experience for users and making your code easier to maintain.
Frequently Asked Questions
What happens if I do not use try...catch with await?
If the promise rejects, the function may throw an unhandled error and stop execution.
Does fetch throw an error for 404 or 500 automatically?
Not always. That is why checking response.ok is important.
Why should I use finally?
Because it runs whether the operation succeeds or fails, which makes it useful for cleanup tasks.