JavaScript
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Types
-
Section 3: Strings and Numbers in Depth
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Objects and Arrays
-
Section 7: Maps, Sets, and Symbols
-
Section 8: Asynchronous JavaScript
-
Section 9: Object-Oriented and Prototypes
-
Section 10: The DOM
-
Section 11: Browser APIs
-
Section 12: Modern JavaScript (ES2015-ES2025)
-
Section 13: Functional Programming Patterns
-
Section 14: Error Handling and Debugging
-
Section 15: Testing
-
Section 16: Accessibility for JavaScript Developers
-
Section 17: Internationalization and Localization
-
Section 18: Performance
-
Section 19: Node.js Fundamentals
-
Section 20: Regular Expressions
-
Section 21: Design Patterns in JavaScript
-
Section 22: Security Basics
-
Section 23: Data Structures and Algorithms in JavaScript
-
Section 24: Practical Projects
-
Section 25: More Advanced Async Patterns
-
Section 26: More Object and Class Practice
-
Section 27: Working with Dates and Internationalization
-
Section 28: Web Components
-
Section 29: More DOM and Browser Practice
-
Section 30: Build Tooling for Vanilla JavaScript
-
Section 31: More Practice Projects
-
Section 32: Interview and Algorithm Practice
-
Section 33: Error Objects (MDN Reference)
-
Section 34: TypedArrays and Binary Data
-
Section 35: Reflection and Metaprogramming (MDN Reference)
-
Section 36: More Global Functions (MDN Reference)
63: Error Handling in Async Code
Imagine you're at a restaurant. You place an order for a burger and a milkshake, and then you go back to your table to chat with your friends. You aren't standing at the counter staring at the chef; you're doing other things while the food is being prepared. That's the essence of async code. But here's the catch: what happens if the kitchen realizes they're out of beef? If the waiter just never comes back to tell you, you'll sit there forever, wondering why your food hasn't arrived. In the coding world, that's a "hanging" promise or an unhandled rejection, and it's one of the fastest ways to crash a Node.js process or leave a user staring at a frozen loading spinner.
Mapping this to our code: placing the order is calling your async function, sitting at the table is your await keyword, and the waiter coming back to tell you the burger is unavailable is your catch block. If you don't provide a way for the "waiter" to report the error, the app just fails silently—or worse, loudly and unexpectedly.
Wrapping the Wait in a Safety Net
When you're using async/await, the most natural way to handle errors is with a try...catch block. It looks a lot like synchronous code, which is why I prefer it. You're essentially saying, "Try to do this sequence of things, and if any of them blow up, jump straight to the catch block."
async function getUserData(userId) {
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
// Pro tip: fetch doesn't actually throw an error on 404 or 500 responses.
// It only throws if the network request itself fails. We have to check manually.
if (!response.ok) {
throw new Error(`User not found! Status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Something went sideways:", error.message);
// Depending on the app, you might return a default user or re-throw the error
return null;
}
}
Notice how I manually threw an error when response.ok was false. I've seen too many junior devs assume that a 404 would automatically trigger the catch block. It doesn't. fetch only rejects the promise if there's a network failure or the server is completely unreachable.
The "All or Nothing" Trap
Now, things get trickier when you're firing off multiple requests at once. You've probably used Promise.all() to speed things up. It's great, but it has a "fail-fast" behavior. If you're fetching data for ten different users and just one of those requests fails, Promise.all() immediately rejects. It doesn't matter if the other nine were successful; the whole operation is marked as a failure.
If you need to know which specific ones failed while still keeping the successful data, Promise.allSettled() is your best friend. It doesn't throw an error; instead, it returns an array of objects describing the outcome of each promise.
async function getMultipleProfiles(ids) {
const promises = ids.map(id => fetch(`https://api.example.com/users/${id}`));
const results = await Promise.allSettled(promises);
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`User ${ids[index]} loaded successfully`);
} else {
console.error(`User ${ids[index]} failed because: ${result.reason}`);
}
});
}
I usually lean towards allSettled when I'm building dashboards. If one widget fails to load its data, I don't want the entire page to crash—I just want that one widget to show an "Error loading data" message while the rest of the page works perfectly.
Avoiding the Global Crash
Lastly, a word of caution: never leave a promise "naked." If you're calling an async function without an await (perhaps you're firing it off in the background), you must attach a .catch() to it. If you don't, and it fails, you get an UnhandledPromiseRejectionWarning. In modern Node.js versions, this can actually terminate your entire process.
// Dangerous: if this fails, the whole app might crash
sendAnalyticsData(data);
// Safe: we've handled the potential failure
sendAnalyticsData(data).catch(err => console.error("Analytics failed, but we'll keep going."));📋 Practical Task
Build a Resilient Multi-City Weather Fetcher
Your task is to create a function called fetchWeatherReports that takes an array of city names. The function should attempt to fetch weather data for each city from a mock API.
Requirements:
- Use
Promise.allSettled()to ensure that one failing city request doesn't stop the others from loading. - Create a helper function
mockFetchWeather(city)that randomly returns a successful response (a JSON object with temperature) or throws an error (e.g., "City not found") to simulate API instability. - The main function should return an object containing two arrays:
successful(containing the data for cities that worked) andfailed(containing the city names that caused errors). - Log the final object to the console to verify that your error handling correctly sorted the successes from the failures.
There are no comments for now.