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)
60: Promise Chaining
I see this mistake constantly when I'm reviewing PRs from junior devs: the assumption that simply calling an asynchronous function inside a .then() block is enough to make the rest of the chain wait for it. It feels intuitive—you're inside a promise chain, so surely the chain knows to wait for the next async task, right?
The "Implicit Wait" Fallacy
Let's look at a concrete example. Imagine we are building a system to process a user's order. We need to verify the user, then check their credit, and finally ship the item. If you write it like this, you've got a problem:
const processOrder = (orderId) => {
verifyUser(orderId)
.then(user => {
// Misconception: I'm calling an async function, so the chain will wait.
checkCredit(user);
})
.then(() => {
console.log("Shipping item..."); // This runs IMMEDIATELY
});
};
Here is why that fails: checkCredit(user) returns a promise, but you aren't doing anything with it. You're just firing it off into the void. Because the first .then() callback doesn't explicitly return that promise, JavaScript assumes the work is done and immediately triggers the next .then() in the chain. Your item ships before the credit check even finishes. In a real production environment, that's how you lose money.
Returning the Promise to Flatten the Chain
To fix this, you have to explicitly return the promise. When you return a promise from a .then() handler, the next .then() in the chain will wait until that specific promise resolves before it executes.
const processOrder = (orderId) => {
verifyUser(orderId)
.then(user => {
// Now we return the promise. The chain "locks" here.
return checkCredit(user);
})
.then(creditStatus => {
// This only runs AFTER checkCredit resolves
console.log(`Credit status: ${creditStatus}. Shipping item...`);
})
.catch(err => {
console.error("Order failed:", err);
});
};
I like to think of this as "flattening." Without the return, you'd be tempted to nest your promises inside each other (the dreaded "Pyramid of Doom"), which makes the code a nightmare to read. By returning the promise, you keep the chain linear.
What happens if you return a plain value?
You might wonder what happens if you return something that isn't a promise—like a string or a number. JavaScript is actually pretty forgiving here. If you return a non-promise value, the chain wraps that value in a resolved promise automatically. It basically says, "Oh, this is already finished," and passes the value straight to the next .then().
fetchUser(id)
.then(user => {
return user.name; // Just a string, not a promise
})
.then(name => {
console.log(`Hello, ${name}`); // Works perfectly
});
The golden rule I follow is this: if the function you're calling inside .then() returns a promise, you must return that promise if you want the subsequent steps to wait for it. If you don't, you're essentially starting a separate, unmanaged asynchronous process that the rest of your chain knows nothing about.
📋 Practical Task
Implementing a Sequential User Permission Validator
You are building a security gate for a dashboard. You need to create a promise chain that performs three sequential asynchronous checks. If any check fails (rejects), the entire chain should jump to the .catch() block.
Requirements:
- Start by calling
fetchUser(userId). - Once the user is fetched, use the result to call
verifyEmail(user). - Once the email is verified, use that result to call
checkAdminPermissions(user). - Finally, log "Access Granted" if all three succeed.
- Ensure you return the promises correctly so the chain executes in order.
Provided Mock Functions:
const fetchUser = (id) => Promise.resolve({ id, name: 'Alex', email: 'alex@example.com' });
const verifyEmail = (user) => Promise.resolve(`Email ${user.email} is verified`);
const checkAdminPermissions = (user) => Promise.resolve('Admin access confirmed');
Write the validateAccess(userId) function to implement this chain.
There are no comments for now.