Error Handling Strategies for async/await in JavaScript
This article explains the fundamentals of async/await in JavaScript, compares it with callbacks and promises, and presents various error‑handling approaches such as try/catch, Promise.catch, combined await‑catch, and global rejection listeners, helping developers choose the most suitable strategy for their projects.
Introduction
In everyday JavaScript development, developers often encounter situations where a page suddenly goes blank, typically because an asynchronous request lacks proper error handling. The elegant syntax of async/await hides a crucial issue: the choice of error‑handling strategy.
When using async/await , many ask whether try/catch is mandatory. The answer is not absolute; it depends on how you design your error‑handling strategy and coding style.
Below we explore the error‑handling mechanism of async/await, the advantages of using try/catch, and alternative methods.
Basic Principles of async/await
Evolution of Asynchronous Code
// Callback hell era
fetchData(url1, (data1) => {
process(data1, (result1) => {
fetchData(url2, (data2) => {
// More nesting...
})
})
})
// Promise era
fetchData(url1)
.then(process)
.then(() => fetchData(url2))
.catch(handleError)
// async/await era
async function workflow() {
const data1 = await fetchData(url1)
const result = await process(data1)
return await fetchData(url2)
}async/await is syntactic sugar over Promise , making asynchronous code look more like synchronous code, which improves readability and writability. An async function always returns a Promise, and you can use await inside it to wait for an asynchronous operation to complete.
If an error occurs during an asynchronous operation (e.g., a network request fails), the error causes the Promise to enter a rejected state.
async function fetchData() {
const response = await fetch("https://api.example.com/data");
const data = await response.json();
return data;
}Using try/catch to Capture Errors
Think of async functions as a high‑speed train, await as the track switch, and uncaught errors as derailments that propagate back through the call stack. try/catch acts as an intelligent protection system that automatically triggers emergency brakes, switches to backup tracks, and sends alerts.
Using try/catch allows you to capture both synchronous and asynchronous errors within the same code block, making error‑handling logic more centralized and intuitive.
Logic is concentrated; error handling is tightly coupled with business logic.
Multiple await operations can be covered.
Suitable for scenarios requiring unified handling or recovery.
async function fetchData() {
try {
const response = await fetch("https://api.example.com/data");
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Error fetching data:", error);
// Handle or re‑throw the error
throw error;
}
}Alternatives to Using try/catch
While try/catch is the most straightforward method, you can also handle errors at the call site instead of inside the async function.
Appending .catch() at the End of a Promise Chain
async function fetchData() {
const response = await fetch("https://api.example.com/data");
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
}
// Caller handles errors
fetchData()
.then(data => {
console.log("Data:", data);
})
.catch(error => {
console.error("Error fetching data:", error);
});This approach moves error handling to the caller, useful when multiple callers need different handling strategies or when you want a global error handler (e.g., React Error Boundary).
Combining await with catch
async function fetchData() {
const response = await fetch('https://api.example.com/data').catch(error => {
console.error('Request failed:', error);
return null; // fallback value
});
if (!response) return;
// Continue processing response...
}Global Error Listening (use with caution, for fallback)
// Browser global listener
window.addEventListener('unhandledrejection', event => {
event.preventDefault();
sendErrorLog({
type: 'UNHANDLED_REJECTION',
error: event.reason,
stack: event.reason.stack
});
showErrorToast('System exception, please contact admin');
});
// Node.js process management
process.on('unhandledRejection', (reason, promise) => {
logger.fatal('Unhandled Promise rejection:', reason);
process.exitCode = 1;
});Error‑Handling Strategy Matrix
Decision Tree Analysis
Is the error recoverable? Is it fatal? Does it require immediate handling? Choose between try/catch, error type checks, Promise.catch, global listeners, or Promise.allSettled based on the scenario.
Error‑Handling Layers
Foundation Layer : ~80% of async operations use try/catch + type checks.
Intermediate Layer : ~15% of generic errors are handled by global interception and logging.
Strategic Layer : ~5% of critical operations implement automatic recovery mechanisms.
Conclusion
My view: Not mandatory, but strongly recommended.
Not mandatory : If you truly have no need to handle errors, you can omit try/catch , but unhandled Promise rejections will crash the program in Node.js or modern browsers.
Recommended : In about 90% of cases you should capture errors, making try/catch the most direct approach.
Overall recommendation: Prefer using try/catch with async/await. Good error handling doesn’t eliminate errors; it gives the system graceful degradation capability.
Your code should be like a skilled pilot—maintaining smooth flight even when encountering turbulence.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.