Understanding Promises in JavaScript
Promises in plain language. Pending, fulfilled, rejected, chaining, Promise.all, race, resolve, reject, with pasteable examples.
Async work is most of web dev. Promises are how JavaScript represents "not done yet" without nesting callbacks six levels deep.
A Promise holds a future value. It starts pending, becomes fulfilled with a value, or rejected with an error. Settling means leaving pending for one of those two.
Pending
const promise = new Promise((resolve, reject) => {
// async work here
// later: resolve(result) or reject(error)
});
Fulfilled
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Operation succeeded!");
}, 2000);
});
promise.then((result) => {
console.log(result);
});
Rejected
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error("Something went wrong!"));
}, 2000);
});
promise.catch((error) => {
console.log(error.message);
});
Chaining
Return a Promise from .then() to chain async steps:
const getUser = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ id: 1, name: "John" });
}, 2000);
});
};
const getUserPosts = (user) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(["Post 1", "Post 2"]);
}, 2000);
});
};
getUser()
.then((user) => getUserPosts(user))
.then((posts) => console.log(posts));
Promise.all
Waits for every Promise in the array. One failure rejects the whole thing.
const fetchUser = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ id: 1, name: "John" });
}, 2000);
});
};
const fetchPosts = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(["Post 1", "Post 2"]);
}, 1500);
});
};
const fetchComments = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(["Comment 1", "Comment 2"]);
}, 1000);
});
};
Promise.all([fetchUser(), fetchPosts(), fetchComments()])
.then(([user, posts, comments]) => {
console.log("User:", user);
console.log("Posts:", posts);
console.log("Comments:", comments);
})
.catch((error) => {
console.log("Error:", error);
});
Promise.race
Settles when the first Promise settles. Handy for timeouts or racing redundant requests.
const fetchResource = (resource, delay) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(`${resource} is fetched successfully in ${delay}ms`);
}, delay);
});
};
const resource1 = fetchResource("Resource 1", 2000);
const resource2 = fetchResource("Resource 2", 1500);
const resource3 = fetchResource("Resource 3", 1000);
Promise.race([resource1, resource2, resource3])
.then((result) => {
console.log(result);
})
.catch((error) => {
console.log(error);
});
Promise.resolve / Promise.reject
Create an already-settled Promise. Useful when an API must return a Promise but your path is sync.
const fetchData = (shouldSucceed) => {
if (shouldSucceed) {
return Promise.resolve("Data fetched successfully");
} else {
return Promise.reject(new Error("Failed to fetch data"));
}
};
fetchData(true)
.then((result) => console.log(result))
.catch((error) => console.log(error));
fetchData(false)
.then((result) => console.log(result))
.catch((error) => console.log(error));
Today
async/await is sugar on top of Promises. You still need to know rejection paths and parallel patterns (all, allSettled). Callback hell was the old pain. Unhandled Promise rejections are the new one.
Learn the states, chain with care, and always attach .catch() (or try/catch in async functions) where failures matter.