Asynchronous JavaScript: Callbacks and Promises
JavaScript Promises
Asynchronous Operations Made Elegant
By AI Learning Assistant · JavaScript · Async · Promises
🤖 AI-POWERED LESSON
This article is paired with a live AI session. As you read, you will see real questions asked to an AI assistant and the answers that came back, showing you exactly how to use AI to master asynchronous JavaScript and Promises.
JavaScript is single threaded, meaning it can only do one thing at a time. But what about waiting for data from a server, reading a file, or setting a timer? If JavaScript stopped everything to wait, your app would freeze. That is where asynchronous programming comes in. And the most elegant way to handle async code today is with Promises.
A Promise is an object that represents the eventual completion (or failure) of an asynchronous operation. Think of it like a restaurant receipt. You order food (start an async task), they give you a receipt (the Promise). That receipt promises that eventually you will get your food (resolve) or hear that they ran out (reject). In the meantime, you can do other things — check your phone, talk to friends — without staring at the kitchen.
We will cover Promise states (pending, fulfilled, rejected), creating Promises with resolve and reject, chaining with .then() and .catch(), Promise.all() for parallel operations, and why Promises solve the nightmare of callback hell.
What is a Promise?
THE RECEIPT ANALOGY · PENDING · FULFILLED · REJECTED
A Promise is an object that acts as a proxy for a value not yet known. When you create a Promise, you give it a function that receives two parameters: resolve and reject. You call resolve(value) when the async work succeeds, and reject(error) when it fails.
A Promise exists in one of three states: Pending (initial state, neither fulfilled nor rejected), Fulfilled (operation completed successfully), or Rejected (operation failed). Once a Promise is settled (fulfilled or rejected), it cannot change state again.
⚠️ ONE TIME ONLY
A Promise can only be resolved or rejected once. Any further calls to resolve or reject are ignored. This guarantees that your async operation has a single, final outcome.
Simulate Fetching Course Data
PRACTICAL PROMISE · setTimeout · RESOLVE & REJECT
Let us simulate loading course data from a server. Real APIs take time, so we use setTimeout to mimic a 2 second network delay. The Promise resolves with course data if loading succeeds, or rejects with an error message if it fails.
📜 COMPLETE PROMISE CODE (with detailed comments)
// Function that returns a Promise simulating an async API call function getCourseData() { // Return a new Promise object return new Promise((resolve, reject) => { // Simulate network delay with setTimeout (2 seconds) setTimeout(() => { // Simulate whether the course loaded successfully const courseLoaded = true; // If loading succeeded, resolve with the course data if (courseLoaded) { resolve({ name: "JS Mastery", cohort: "01", students: 100 }); } // If loading failed, reject with an error message else { reject("Failed to load course data"); } }, 2000); // 2000 milliseconds = 2 seconds }); } // Using the Promise: .then() handles success, .catch() handles errors getCourseData() .then(data => { console.log("Course loaded:", data.name); console.log("Cohort:", data.cohort); console.log("Students:", data.students); }) .catch(error => { console.log("Error:", error); });
✅ WHAT'S HAPPENING HERE
1. getCourseData() creates and returns a new Promise. 2. Inside the Promise, setTimeout simulates a 2 second network request. 3. After 2 seconds, if courseLoaded is true, we call resolve() with the course data. 4. If false, we call reject() with an error. 5. When calling the function, .then() runs on success, .catch() runs on failure. The code is clean, readable, and doesn't block other operations.
Promise Chaining
THEN RETURNS A NEW PROMISE · SEQUENTIAL OPERATIONS
One of the most powerful features of Promises is chaining. When you return a value from a .then() handler, it automatically wraps that value in a resolved Promise. If you return another Promise, it will wait for that Promise to settle before moving to the next .then(). This lets you sequence asynchronous operations without nesting.
🎯 CATCH CATCHES EVERYTHING
A single .catch() at the end of a chain will catch any rejection that occurs anywhere in the chain. This is like a try-catch block for your entire asynchronous sequence.
Promise.all()
PARALLEL OPERATIONS · WAIT FOR ALL
Sometimes you need to run multiple asynchronous operations at the same time and wait for all of them to finish. Promise.all() takes an array of Promises and returns a new Promise that resolves with an array of results when all Promises resolve. If any Promise rejects, the whole thing rejects immediately.
Quick Comparison
| FEATURE | Callbacks | Promises |
|---|---|---|
| Readability | Pyramid nesting | Flat chain |
| Error Handling | Each callback needs its own | Single .catch() |
| Chaining | Manual nesting | Automatic with .then() |
| Parallel Operations | Manual counter logic | Promise.all() |
| State Management | No standard | Pending, Fulfilled, Rejected |
🤖 HOW AI ACCELERATES THIS TOPIC
Ask AI: "Convert this callback-based function to use Promises." Paste your old code and get modern Promise syntax instantly.
Ask: "Explain why my Promise chain is skipping the second .then()" The AI will check if you forgot to return a value or Promise.
Ask: "Write a function that fetches three APIs in parallel using Promise.all()" Get production-ready code with error handling.
AI-Assisted JavaScript Learning · Promises & Async · Write Clean, Non-Blocking Code
Complete this lesson
Mark as complete to track your progress