Asynchronous JavaScript
Promises
Master JavaScript Promises. Learn Promise states, how to wrap asynchronous operations, chain .then() and .catch(), and manage error flow.
1. Introduction
A Promise is a proxy placeholder object representing the eventual completion (or failure) of an asynchronous operation and its resulting value. Promises solve the inversion of control issues of callbacks by providing a standardized API for handling asynchronous events.
2. Why It Matters
Promises are the foundation of modern asynchronous JavaScript APIs (like the fetch API). They allow you to chain operations, propagate errors to a single catch block, and handle asynchronous events cleanly.
3. Real-World Analogy
Think of a Pager at a Restaurant:
- Pending State: You order food. The host hands you a plastic pager (the Promise). Your food is not ready, but you have a placeholder for it.
- Fulfilled State: Your food is ready. The pager buzzes and flashes (resolves the promise). You swap the pager for your meal (resolved value).
- Rejected State: The kitchen runs out of ingredients. The pager flashes red (rejects the promise), signaling that the order failed. You check in at the desk to handle the error.
4. Promise States
A Promise is always in one of three mutually exclusive states:
• Pending: The initial state. The asynchronous operation is still running.
• Fulfilled: The operation completed successfully. The promise has a resolved value.
• Rejected: The operation failed. The promise has a rejection reason (error).
Once a Promise resolves or rejects, its state is settled and cannot change.
5. Chaining & Error Propagation
Methods like .then() and .catch() always return a new Promise, allowing you to flatten asynchronous pipelines into a single chain. Errors propagate down the chain automatically until they reach the first .catch() block.
6. Practical Example
This script shows how to wrap a classic callback-based timer inside a Promise:
7. Common Mistakes
- Forgetting to return nested Promises: If you omit the
returnkeyword inside a.then()callback, the next.then()block in the chain runs immediately with the valueundefinedinstead of waiting for the nested promise to resolve.
8. Quick Quiz
Q1: Can a Promise change its state after it has transitioned from Pending to Fulfilled?
A) Yes, if we call reject() later
B) No, the state of a settled Promise is immutable
Answer: B — Once a Promise resolves or rejects, its state is settled and cannot be modified again.
9. Scenario-Based Challenge
The API Retry Policy:
You write a network fetcher. If the network request fails, you want to retry the request up to 3 times before finally rejecting the Promise. Design a recursive Promise wrapper to implement this retry logic.
10. Debugging Exercise
Explain why this error handler is never triggered, and how to fix it:
const p = new Promise((resolve, reject) => { setTimeout(() => { throw new Error('Async crash'); // throws inside a background timer! }, 100); });
p.catch(err => console.log('Caught: ' + err.message)); // never runs!
View Solution
Diagnosis: The exception is thrown inside a setTimeout callback, which runs in a different execution context than the Promise constructor. As a result, the Promise cannot catch the error. The error is thrown globally as an uncaught exception, and the Promise remains in a pending state indefinitely.
Fix: Catch the error inside the timeout callback, and reject the Promise explicitly:
const p = new Promise((resolve, reject) => {
setTimeout(() => {
try {
throw new Error('Async crash');
} catch (e) {
reject(e); // Reject the Promise explicitly
}
}, 100);
});
11. Interview Questions
🟢 Q1: Describe the three states of a Promise and the concept of settling.
Answer:
• Pending: The initial state before the operation finishes.
• Fulfilled: The operation completed successfully. The Promise resolves to a value.
• Rejected: The operation failed. The Promise rejects with a reason (error).
• Settling: A Promise is settled when it transitions to either the fulfilled or rejected state. Once settled, its state and value/error are locked and cannot change.
12. Production Considerations
- • Handle Unhandled Rejections: Always add a
.catch()block to your Promise chains. In Node.js or browser production environments, monitor global unhandled rejection events (like theunhandledrejectionwindow listener) to prevent silent application crashes.