JavaScript Promises
Complete Study Notes
Concepts · Worked Examples · Practice Questions
Topics covered: Promise creation, chaining, error handling, [Link] / race / allSettled / any,
.finally(), and real-world patterns
Section 1 — What is a Promise?
1.1 Definition
A Promise is a special JavaScript object that acts as a placeholder for the eventual result of an
asynchronous operation. Think of it like placing an order at a restaurant — you don't get the
food instantly, but you receive a ticket (the Promise) that will eventually be fulfilled when your
food is ready, or rejected if the kitchen runs out of ingredients.
Promises were introduced in ES6 (2015) to replace messy callback patterns and give
developers a cleaner, more readable way to write asynchronous code.
1.2 The Three States
Every Promise lives in exactly one of these three states at any moment:
pending → The operation has started but not finished yet
fulfilled → The operation completed successfully (resolve was called)
rejected → The operation failed (reject was called)
Once a Promise moves from pending to fulfilled or rejected, it is settled and cannot change state
again.
REMEMBER: A Promise can only settle once. Calling resolve() and then reject()
on the same Promise does nothing — whichever runs first wins.
1.3 Core Syntax — Creating a Promise
You create a Promise by calling new Promise() and passing it an executor function. The
executor receives two arguments: resolve (call it when your operation succeeds) and reject (call
it when something goes wrong).
const myPromise = new Promise((resolve, reject) => {
// Put your asynchronous logic here
// e.g. setTimeout, file read, database query, etc.
const everythingWentWell = true; // pretend this is real logic
if (everythingWentWell) {
resolve("Operation succeeded!"); // fulfilled state
} else {
reject("Something went wrong!"); // rejected state
}
});
1.4 Consuming a Promise — .then() and .catch()
After creating a Promise you consume it using .then() for success and .catch() for failure. Both
return new Promises, which is what makes chaining possible.
myPromise
.then((result) => {
// This runs if resolve() was called
[Link](result);
})
.catch((error) => {
// This runs if reject() was called OR if any .then() threw an error
[Link](error);
});
1.5 Quick-Reference Table
Concept What it means
new Promise(fn) Creates a new Promise; fn runs immediately (synchronously)
resolve(value) Moves the Promise to fulfilled state; value reaches .then()
reject(reason) Moves the Promise to rejected state; reason reaches .catch()
.then(cb) Registers a success handler; returns a new Promise
.catch(cb) Registers an error handler; catches any upstream rejection or
throw
.finally(cb) Runs cb whether fulfilled or rejected; useful for cleanup
[Link](arr) Runs all Promises in parallel; fails fast if any one rejects
[Link](arr) Settles as soon as the first Promise settles (success or failure)
[Link](arr) Waits for all Promises; returns results for every one, even failures
[Link](arr) Resolves as soon as the FIRST success occurs; ignores rejections
Section 2 — Worked Examples
Example 1 — checkNumber (Basic resolve / reject)
Write a function checkNumber(num) that returns a Promise. If num is greater than 10 the
Promise should resolve with the string "number is big". Otherwise it should reject with "number
is small". Call the function with the value 20 and print whichever message comes back.
function checkNumber(num) {
return new Promise((resolve, reject) => {
if (num > 10) {
resolve("number is big");
} else {
reject("number is small");
}
});
}
checkNumber(20)
.then((message) => {
[Link](message); // → number is big
})
.catch((message) => {
[Link](message);
});
KEY INSIGHT: The executor function (resolve, reject) runs synchronously.
The .then() and .catch() callbacks are always called asynchronously (in a
microtask queue), even if resolve() was called synchronously inside the executor.
Example 2 — delayedMessage (setTimeout inside a Promise)
Write a function delayedMessage() that returns a Promise. After exactly 2 seconds the Promise
should resolve with an object: { name: '2 seconds passed', message: 'promise resolved' }. In the
.then() callback, print both properties on a single line.
function delayedMessage() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
name: '2 seconds passed',
message: 'promise resolved'
});
}, 2000); // ← arrow function wraps resolve — DO NOT write
resolve()
});
}
delayedMessage()
.then((obj) => {
[Link]([Link] + ' ' + [Link]);
// → 2 seconds passed promise resolved
})
.catch((err) => [Link](err));
COMMON MISTAKE: setTimeout(resolve(), 2000) ← WRONG (calls resolve immediately!)
CORRECT: setTimeout(() => resolve(), 2000) OR setTimeout(resolve, 2000)
Example 3 — Promise Chaining (sequential async steps)
You have three async functions: getUser(), getPosts(), and getComments(). Each returns a
Promise that resolves with an object { name, message }. Chain these three calls so they run one
after another and print each result. If any step fails, a single .catch() at the end should handle it.
// Assume getUser, getPosts, getComments are already defined and
// each returns a Promise that resolves with { name, message }
getUser()
.then((obj) => {
[Link]([Link] + ' — ' + [Link]);
return getPosts(); // ← MUST return! Otherwise chain breaks
})
.then((obj) => {
[Link]([Link] + ' — ' + [Link]);
return getComments();
})
.then((obj) => {
[Link]([Link] + ' — ' + [Link]);
})
.catch((err) => {
[Link]('Something failed:', err);
});
CHAINING RULE: Always return the next Promise inside .then().
Forgetting return means the next .then() receives undefined, not the
resolved value — a silent bug that is very hard to trace.
Example 4 — Error Propagation (random failure)
Modify getPosts() so it randomly fails 50% of the time. Use [Link]() < 0.5 inside a
setTimeout to either reject with 'Failed to fetch posts' or resolve normally. Plug this into the
chain from Example 3 and observe that a single .catch() at the end catches the failure
regardless of which step breaks.
function getPosts() {
return new Promise((resolve, reject) => {
setTimeout(() => {
if ([Link]() < 0.5) {
reject("Failed to fetch posts");
} else {
resolve({ name: 'posts', message: 'user posts received' });
}
}, 1000);
});
}
// Chain works exactly as before — .catch() handles all failures
getUser()
.then((obj) => { [Link]([Link]); return getPosts(); })
.then((obj) => { [Link]([Link]); return getComments(); })
.then((obj) => { [Link]([Link]); })
.catch((err) => { [Link]("Caught:", err); });
Error propagation means: the moment any .then() throws or any Promise rejects, the engine
skips every remaining .then() and jumps straight to the nearest .catch(). You do not need
a .catch() at every step.
Example 5 — [Link] (parallel execution)
Instead of fetching user, posts, and comments one by one, fire all three requests at the same
time using [Link](). Print each result in a loop. If any single request fails the entire
[Link]() rejects immediately — add a .catch() to handle that.
[Link]([
getUser(),
getPosts(),
getComments()
])
.then((results) => {
// results is an array in the SAME ORDER as the input array
[Link]((obj) => {
[Link]([Link] + ': ' + [Link]);
});
})
.catch((err) => {
[Link]("One of the requests failed:", err);
});
[Link] resolves in parallel — all three requests start simultaneously.
Total time ≈ the SLOWEST single request, not the sum of all three.
Order of results matches order of input array, NOT order of completion.
Section 3 — Practice Questions
All questions below must be solved using plain Promises (.then / .catch / .finally). Do NOT use
async/await or the fetch API.
For each question: write the complete solution in your editor, run it, verify the output, and then
try deliberately breaking it to see how errors behave.
Practice Q1 — Delayed Greeting
Scenario: You are building a splash screen for an app. After 1 second you want to display a
welcome message before the rest of the UI loads.
Task: Write a function called showWelcome() that returns a Promise. After 1000 ms the Promise
should resolve with the string "Welcome! App is ready.". In .then(), print the message to the
console.
Expected output (after 1 second):
Welcome! App is ready.
Extension: After you get it working, change the delay to 3000 ms and observe the difference.
Practice Q2 — Coin Flip (Random resolve / reject)
Scenario: You are writing a game where a coin is flipped. Heads means the player wins; tails
means they lose.
Task: Write a function flipCoin() that returns a Promise. Use [Link]() — if the value is >=
0.5, resolve with "Heads — You win!"; otherwise reject with "Tails — You lose!". Call flipCoin()
five times in a row using a loop and print the result of each flip.
Expected output (random each run, e.g.):
Heads — You win!
Tails — You lose!
Heads — You win!
Heads — You win!
Tails — You lose!
Practice Q3 — User Login Chain
Scenario: When a user logs in, the app must (1) verify credentials, then (2) load the user's
profile. These two steps must happen in order — you cannot load the profile before login
succeeds.
Task: Create two functions: loginUser() and loadProfile(). Both return Promises that resolve
after a short setTimeout (500 ms each) with simple objects: loginUser resolves with { status:
'logged in', userId: 42 } and loadProfile resolves with { name: 'Arjun', role: 'admin' }. Chain them
so the userId from step 1 is passed into step 2, and print both results.
Expected output (after ~1 second total):
Login successful. User ID: 42
Profile loaded: Arjun (admin)
HINT: In the first .then() you receive the login result. Return loadProfile(userId)
so the next .then() can receive the profile.
Practice Q4 — Parallel Data Load with [Link]
Scenario: A dashboard needs to show three independent widgets: weather data, stock prices,
and news headlines. All three API calls are independent, so there is no reason to wait for one
before starting another.
Task: Create three functions — getWeather(), getStockPrice(), and getNewsHeadline() — each
returning a Promise that resolves after a different delay (300 ms, 500 ms, 700 ms respectively)
with an object of your choice. Use [Link]() to load all three simultaneously, then print all
three results once every Promise has resolved.
Expected output (after ~700 ms):
Weather: Sunny, 32°C
Stock: AAPL $193.45
News: Markets close higher after Fed decision
Practice Q5 — Observing [Link] Fail-Fast Behaviour
Scenario: You are fetching user data, cart items, and payment details for a checkout page. The
payment details endpoint is unreliable.
Task: Take your three functions from Q4 (or create new ones). Make the middle function (e.g.
getStockPrice or getCartItems) randomly reject 100% of the time — hard-code a reject() call.
Pass all three into [Link]() and verify in .catch() that only the rejection message is printed,
and the other two results are silently discarded.
Expected output:
[Link] failed: Payment service unavailable
Reflection question (write the answer in a comment): What happens to the other two Promises
that already resolved? Are their values accessible anywhere?
Practice Q6 — Audit Log with [Link]
Scenario: You are sending notifications to three different services (email, SMS, push
notification). Even if one service fails you still want to know the result of every single attempt so
you can log them all.
Task: Create sendEmail(), sendSMS(), and sendPush(). Make sendSMS() always reject with
"SMS gateway timeout". Use [Link]() and loop over the results array. For each
result print whether it was fulfilled or rejected and the corresponding value or reason.
Expected output:
email → fulfilled : Email sent successfully
SMS → rejected : SMS gateway timeout
push → fulfilled : Push notification delivered
allSettled() result objects have a 'status' property ('fulfilled' or 'rejected'),
a 'value' property (if fulfilled), and a 'reason' property (if rejected).
Practice Q7 — CDN Failover with [Link]
Scenario: Your app loads a large configuration file. You have two CDN servers. Whichever
responds first should be used, saving the user time.
Task: Create loadFromCDN1() (resolves after 2000 ms) and loadFromCDN2() (resolves after
800 ms), both resolving with a string like "Config from CDN-1" / "Config from CDN-2". Use
[Link]() to pick the winner and print it.
Expected output (after ~800 ms):
Winner: Config from CDN-2
Extension: Swap the delays so CDN-1 is faster. Confirm the output changes accordingly.
Practice Q8 — First-Server-Fails Race
Scenario: Same CDN setup as Q7, but now CDN-1 is the faster server — except it crashes
immediately. You want to observe how [Link]() handles a rejection winning the race.
Task: Create cdnFast() that rejects after 300 ms with "CDN crashed!" and cdnSlow() that
resolves after 1500 ms. Race them. Confirm that .catch() is triggered (not .then()), because the
first settler is a rejection.
Expected output:
Race lost to an error: CDN crashed!
Reflection: How is this different from [Link]()? (Answer in a comment in your code.)
Practice Q9 — Database Connection Cleanup with .finally()
Scenario: A function opens a database connection, runs a query, and must close the connection
whether the query succeeded or failed — otherwise the connection leaks.
Task: Write a function runQuery() that resolves 70% of the time with "Query returned 5 rows"
and rejects 30% of the time with "Query timed out". Chain .then(), .catch(), and .finally().
In .finally() print "Database connection closed." Confirm it always prints regardless of outcome.
Expected output (success case):
Query returned 5 rows
Database connection closed.
Expected output (failure case):
Error: Query timed out
Database connection closed.
Practice Q10 — Error Thrown Inside .then()
Scenario: You are processing an API response. The request itself succeeds but the response
data is malformed, so your parsing code throws an error. You want to verify that a
single .catch() at the end handles both network errors AND processing errors.
Task: Create a function fetchConfig() that resolves after 500 ms with the string
"INVALID_JSON". In the first .then() try to parse the value — use [Link]() on a
deliberately broken string or throw new Error("Malformed response data") manually. Verify that
the .catch() at the end receives the thrown error.
Expected output:
Caught downstream error: Malformed response data
KEY POINT: Any exception thrown INSIDE a .then() callback is automatically
converted into a rejection and forwarded to the nearest .catch(). You do
not need try/catch inside .then() — the Promise chain handles it for you.
Section 4 — Critical Rules & Common Mistakes
Rule 1 — Always return inside .then() when chaining
This is the most common mistake beginners make with Promises.
Wrong — chain is silently broken
getUser()
.then((user) => {
getPosts([Link]); // No return! Next .then() gets undefined
})
.then((posts) => {
[Link](posts); // prints: undefined
});
Correct
getUser()
.then((user) => {
return getPosts([Link]); // Return the Promise
})
.then((posts) => {
[Link](posts); // prints the actual posts data
});
Rule 2 — setTimeout callback MUST wrap resolve
// WRONG — resolve() is called immediately when the Promise is created
new Promise((resolve) => { setTimeout(resolve(), 2000); })
// CORRECT — resolve is called after 2 seconds
new Promise((resolve) => { setTimeout(() => resolve(), 2000); })
// OR
new Promise((resolve) => { setTimeout(resolve, 2000); })
Rule 3 — throw and reject both reach .catch()
// Both of these end up in .catch()
new Promise((resolve, reject) => { reject('reason'); })
new Promise((resolve, reject) => { throw new Error('message'); })
// A throw inside .then() also reaches .catch()
somePromise
.then(() => { throw new Error('processing failed'); })
.catch((err) => [Link]([Link])); // 'processing failed'
Rule 4 — Error propagation skips all .then() blocks
The moment a rejection occurs (or a throw happens), every subsequent .then() is skipped and
execution jumps directly to the next .catch().
step1() // rejects here
.then(step2) // SKIPPED
.then(step3) // SKIPPED
.then(step4) // SKIPPED
.catch((err) => [Link]("Caught at the end:", err)); // RUNS
Rule 5 — [Link] vs [Link] vs [Link] vs
[Link]
Concept What it means
[Link] All must succeed. One failure → entire thing rejects immediately.
[Link] Always waits for ALL. Returns every result (fulfilled or rejected).
[Link] First SUCCESS wins. Rejections are ignored until all fail.
[Link] First to settle (success OR failure) wins. Others are ignored.
Rule 6 — .finally() does not receive a value
somePromise
.then((val) => {
[Link](val); // receives the resolved value
return val;
})
.catch((err) => {
[Link](err); // receives the rejection reason
})
.finally(() => {
// Receives NO arguments — perfect for cleanup code
[Link]('Done. Closing resources.');
});
Section 5 — Mental Models & Quick-Reference
5.1 The Promise Flow Diagram
Think of a Promise chain like a water pipe. Water (your data) flows through until a blockage
(error) occurs — at that point it jumps to the nearest drain (.catch()).
create Promise
|
▼
[pending]
|
┌──┴──┐
▼ ▼
resolve reject
| |
▼ ▼
.then() .catch()
| |
└───┬────┘
▼
.finally() (always runs)
5.2 Execution Order — Sync vs Async
Promise callbacks are always microtasks — they run after the current synchronous code
finishes but before setTimeout callbacks. Understanding this prevents subtle ordering bugs.
[Link]('1 — synchronous');
[Link]('resolved').then((v) => {
[Link]('3 — microtask:', v);
});
[Link]('2 — also synchronous');
// Output order:
// 1 — synchronous
// 2 — also synchronous
// 3 — microtask: resolved
5.3 Returning Values vs Promises from .then()
// Returning a plain value wraps it in a resolved Promise automatically
[Link](5)
.then((n) => n * 2) // returns 10 (plain value)
.then((n) => n + 1) // receives 10, returns 11
.then((n) => [Link](n)); // prints 11
// Returning a Promise lets the chain wait for it
[Link](5)
.then((n) => new Promise((res) => setTimeout(() => res(n * 2), 1000)))
.then((n) => [Link](n)); // prints 10 after 1 second
5.4 Summary Checklist — Before You Submit Any Promise Code
• Did you return the Promise inside every .then() that chains to another?
• Did you wrap resolve/reject inside an arrow function when using setTimeout?
• Does every Promise chain have a .catch() at the end?
• If using [Link], do you understand that ONE failure kills the batch?
• Did you test both the success AND failure paths of every function?
• Are you using .finally() for any cleanup that must always run?
You are strong in Promises. Next step: async / await
async / await is just Promises with cleaner syntax — everything you learned here transfers directly.