Introduction
Asynchronous programming is a core part of modern JavaScript. Promises and async/await make async code easier to write and read, but error handling can still be confusing. Many bugs happen because promise errors are not handled correctly.
In simple words, if a promise fails and you do not catch the error, your application may crash, behave unexpectedly, or fail silently. In this article, you will learn the correct way to handle asynchronous errors in JavaScript promises using clear explanations and easy examples.
What Is an Asynchronous Error in JavaScript?
An asynchronous error occurs after an async operation starts, such as a network request, file read, or timer.
Example:
fetch('/api/data')
.then(response => response.json());
If the request fails and the error is not handled, JavaScript raises an unhandled promise rejection.
Handling Errors Using .catch()
The most basic and important rule is to always use .catch() when working with promises.
Example:
fetch('/api/data')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
Explanation:
.catch()handles any error in the promise chainIt prevents unhandled promise rejections
This is the correct way to handle errors when using .then().
Returning Promises Correctly
A common mistake is forgetting to return a promise inside .then().
Wrong example:
fetch('/api/data')
.then(response => {
response.json();
})
.catch(error => console.error(error));
Correct example:
fetch('/api/data')
.then(response => {
return response.json();
})
.then(data => console.log(data))
.catch(error => console.error(error));
Returning the promise ensures errors propagate correctly.
Handling Errors with async/await and try-catch
The recommended modern approach is using async/await with try-catch blocks.
Example:
async function loadData() {
try {
const response = await fetch('/api/data');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Failed to load data:', error);
}
}
Explanation:
trycontains async codecatchhandles any error thrown insidetry
This approach is clean, readable, and easy to debug.
Throwing Errors Inside Promises
You can throw custom errors inside promises, and they will be caught by .catch() or try-catch.
Example:
async function getUser(userId) {
if (!userId) {
throw new Error('User ID is required');
}
return { id: userId, name: 'Rahul' };
}
Thrown errors behave like rejected promises.
Handling Multiple Promises Safely
When working with multiple promises, error handling becomes more important.
Example with Promise.all:
Promise.all([fetch('/api/a'), fetch('/api/b')])
.then(results => console.log(results))
.catch(error => console.error('One request failed:', error));
If any promise fails, .catch() is triggered.
Avoiding Unhandled Promise Rejections
Unhandled promise rejections happen when errors are not caught.
Bad example:
async function run() {
fetch('/api/data');
}
Good example:
async function run() {
try {
await fetch('/api/data');
} catch (error) {
console.error(error);
}
}
Always handle promises explicitly.
Global Error Handling (Last Resort)
You can listen for unhandled promise rejections globally.
Example:
window.addEventListener('unhandledrejection', event => {
console.error('Unhandled promise rejection:', event.reason);
});
This should be used only as a safety net, not as the main error-handling strategy.
Comparison Table: .then/.catch vs async/await
The table below helps you quickly understand the differences between the two common approaches.

Join the conversation! Your thoughts help the community grow.