Understanding Promises in JavaScript

Asynchronous programming is one of the most important concepts in JavaScript.
Operations like:
API requests
Database queries
File reading
Timers
Authentication
Network communication
do not complete immediately.
Originally, JavaScript handled asynchronous operations using callbacks.
But as applications became larger, callback-based code became difficult to manage.
To solve this problem, JavaScript introduced:
Promises
Promises made asynchronous code cleaner, more readable, and easier to maintain.
In this article, we will understand:
What problem promises solve
Promise states
Promise lifecycle
Handling success and failure
Promise chaining
Why promises improved async programming
The Problem Before Promises
Before promises, asynchronous operations mostly used callbacks.
Example:
readFile("data.txt", (err, data) => {
if (err) {
console.log(err);
} else {
fetchUser(data, (err, user) => {
if (err) {
console.log(err);
} else {
saveUser(user, (err) => {
if (err) {
console.log(err);
}
});
}
});
}
});
As more async operations were added, the code became deeply nested.
This created:
Poor readability
Difficult debugging
Complex error handling
This problem became known as:
Callback Hell
Why Promises Were Introduced
Promises were introduced to improve:
Readability
Async flow control
Error handling
Maintainability
Promises allow asynchronous operations to be handled in a cleaner and more structured way.
What Is a Promise?
A promise represents:
A future value
It is an object that eventually:
Resolves successfully
Fails with an error
Think of a promise as:
"A placeholder for a result that will arrive later."
Real-World Analogy
Imagine ordering food online.
After placing the order:
Food is not available immediately
Restaurant promises delivery later
Possible outcomes:
Food delivered successfully
Delivery failed
This is similar to JavaScript promises.
Creating a Basic Promise
Example:
const promise = new Promise((resolve, reject) => {
const success = true;
if (success) {
resolve("Task completed");
} else {
reject("Task failed");
}
});
Understanding resolve and reject
Inside promises:
resolve()
Used when operation succeeds.
Example:
resolve("Success");
reject()
Used when operation fails.
Example:
reject("Error occurred");
Promise States
A promise can exist in three states.
Pending
Initial state.
Operation is still running.
Fulfilled
Operation completed successfully.
Rejected
Operation failed.
Promise Lifecycle Diagram
PENDING
/ \
/ \
V V
FULFILLED REJECTED
A promise starts pending and later settles into one final state.
Promise Lifecycle Example
Example:
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Completed");
}, 2000);
});
Flow:
Promise created
State = pending
Wait 2 seconds
resolve() executes
State = fulfilled
Handling Promise Success
Success is handled using:
.then()
Example:
promise.then((result) => {
console.log(result);
});
Output:
Completed
Handling Promise Failure
Errors are handled using:
.catch()
Example:
promise.catch((error) => {
console.log(error);
});
This handles rejected promises.
Handling Success and Failure Together
Example:
const promise = new Promise((resolve, reject) => {
const success = false;
if (success) {
resolve("Success");
} else {
reject("Something went wrong");
}
});
promise
.then((result) => {
console.log(result);
})
.catch((error) => {
console.log(error);
});
Output:
Something went wrong
Promise Execution Flow
Promise Created
|
V
Pending State
|
V
Success? -----> Yes -----> .then()
|
No
|
V
.catch()
Why Promises Improved Readability
Promises replaced deeply nested callbacks with cleaner chaining.
Callback Version
getUser((user) => {
getPosts(user, (posts) => {
getComments(posts, (comments) => {
console.log(comments);
});
});
});
Promise Version
getUser()
.then(getPosts)
.then(getComments)
.then(console.log)
.catch(console.error);
This structure is much cleaner.
Callback vs Promise Comparison
CALLBACKS
Task
|
V
Nested Task
|
V
More Nesting
PROMISES
Task
|
V
.then()
|
V
.then()
|
V
.catch()
Promises flatten async workflows.
Understanding Promise Chaining
One major advantage of promises is chaining.
Each .then() can return another promise.
Example:
fetchUser()
.then((user) => {
return fetchPosts(user.id);
})
.then((posts) => {
console.log(posts);
});
This creates sequential async execution without deep nesting.
Why Promise Chaining Matters
Chaining improves:
Readability
Flow control
Sequential execution
Error handling
Large async workflows become easier to manage.
Automatic Error Propagation
One powerful promise feature:
Errors automatically travel down the chain.
Example:
fetchUser()
.then(fetchPosts)
.then(savePosts)
.catch(console.error);
Any failure triggers .catch().
This simplified async error handling significantly.
Common Promise Methods
| Method | Purpose |
|---|---|
.then() |
Handle success |
.catch() |
Handle errors |
.finally() |
Execute always |
Promise.all() |
Run multiple promises |
Promise.race() |
First completed promise |
Understanding finally()
Example:
promise
.then(() => {
console.log("Success");
})
.catch(() => {
console.log("Error");
})
.finally(() => {
console.log("Completed");
});
finally() executes regardless of success or failure.
Why Promises Became Important
Promises solved major async programming problems:
Callback hell
Nested logic
Scattered error handling
Poor readability
They became the foundation for modern async JavaScript.
Promises and Async/Await
Modern async/await syntax is actually built on top of promises.
Example:
async function getData() {
const data = await fetchUser();
console.log(data);
}
Internally, async/await still uses promises.
Common Beginner Mistakes
Forgetting return in Chains
Bad example:
.then(() => {
fetchData();
})
Better:
.then(() => {
return fetchData();
})
Missing catch()
Unhandled promise errors can crash applications.
Always handle rejections properly.
Mixing Callbacks and Promises Unnecessarily
Avoid mixing patterns unless required.
Common Interview Questions
What Is a Promise?
An object representing a future async result.
What Are Promise States?
Pending
Fulfilled
Rejected
Difference Between Callback and Promise
Promises improve readability and error handling.
What Does .then() Do?
Handles successful promise resolution.
Real-World Importance
Promises are used everywhere in modern JavaScript:
API requests
Database operations
Authentication systems
File uploads
Node.js backends
React applications
Understanding promises is essential before learning async/await.