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)
62: Async/Await
A few years ago, I was reviewing a PR from a junior dev who was trying to build a user dashboard. He had written a chain of five different .then() blocks to fetch a user, then their preferences, then their recent orders, and finally the shipping status for each order. The code looked like a giant sideways pyramid, and he was struggling to pass a variable from the first .then() all the way down to the fifth one. He'd spent half his afternoon fighting with scope and "Promise { <pending> }" logs in the console. That's when I sat him down and showed him async/await. It didn't actually change how JavaScript handles the event loop, but it completely changed how he was able to reason about his code.
Writing Code That Reads Like a Story
At its core, async/await is syntactic sugar built on top of Promises. You aren't replacing Promises; you're just using a cleaner way to consume them. When you mark a function with the async keyword, you're telling JavaScript that this function will return a Promise, regardless of what you actually return inside it. But the real magic happens with await.
When you await a Promise, the execution of that specific function pauses until the Promise resolves. The rest of your application keeps running—the browser doesn't freeze—but inside that function, the code reads linearly. Look at the difference in this common scenario where we need to fetch a user and then their specific posts:
// The old "Promise Chain" way
function getUserData(userId) {
return fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(user => {
return fetch(`/api/posts?userId=${user.id}`)
.then(res => res.json())
.then(posts => {
return { user, posts };
});
});
}
// The async/await way
async function getUserData(userId) {
const userRes = await fetch(`/api/users/${userId}`);
const user = await userRes.json();
const postsRes = await fetch(`/api/posts?userId=${user.id}`);
const posts = await postsRes.json();
return { user, posts };
}
Notice how in the second version, we don't have to nest our logic to keep the user object in scope for the second request. We just declare it and use it on the next line. It's much easier on the eyes and significantly easier to debug.
Managing Failures with Try and Catch
One thing that trips people up is error handling. With Promise chains, you just tack a .catch() onto the end. With async/await, we go back to a classic programming pattern: the try...catch block. I actually prefer this because it allows you to wrap multiple asynchronous calls—and any synchronous logic in between them—in a single error-handling net.
If any await call rejects, the execution jumps straight to the catch block. This prevents your app from crashing due to an "Unhandled Promise Rejection," which is a nightmare to track down in production. I usually recommend wrapping your await calls like this:
async function loadDashboard() {
try {
const data = await fetchCriticalData();
renderUI(data);
} catch (error) {
console.error("Dashboard failed to load:", error);
showErrorMessageToUser("Something went wrong. Please refresh.");
}
}
Just a quick heads-up: don't over-use await. If you have two API calls that don't depend on each other, awaiting them one after the other creates a bottleneck. In those cases, you'll want to kick them both off and use Promise.all(), which we'll touch on in a later lesson. For now, focus on the clarity that async/await brings to your sequential logic.
📋 Practical Task
Exercise: Building a Sequential GitHub Profile Fetcher
Your task is to create a script that fetches a GitHub user's profile and then uses the url from their blog field to fetch the HTML of that blog page. This requires sequential asynchronous calls.
Requirements:
- Create an
asyncfunction calledfetchUserAndBlog(username). - Use
await` with the Fetch API to get user data fromhttps://api.github.com/users/{username}. - Extract the
blogURL from the resulting JSON. - Use
await` again to fetch the content of that blog URL. - Wrap the entire process in a
try...catchblock to handle cases where the user doesn't exist or the blog URL is invalid. - Log the user's name and the first 100 characters of the blog's HTML to the console.
There are no comments for now.