JavaScript Promises: Complete Beginner Guide
Promises are the foundation of async JavaScript. Learn what a promise is, the three promise states, how .then() and .catch() work, and how to create your own promises.
A promise is a JavaScript object that represents a value that does not exist yet but will exist later. Think of it like a ticket at a restaurant: you order food, you get a buzzer, and when the buzzer goes off, your food is ready.
The buzzer is the promise. The food is the value.
Before promises, async JavaScript used callbacks: functions passed to other functions that run when the work is done. Callbacks work, but they lead to deeply nested code called "callback hell." Promises flatten that nesting and give you a standard way to handle both success and failure.
// A promise that resolves after 1 second
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Here is your data!");
}, 1000);
});
myPromise.then((value) => {
console.log(value);
});
console.log("Waiting...");Notice the order: "Waiting..." logs immediately.
The promise's value only logs a full second later, once the timer inside the executor fires and calls resolve.
Waiting...
(after 1 second)
Here is your data!The promise is created, a one-second timer starts, and the code after it runs immediately. When the timer fires, the callback in .then() runs.
The three promise states
Every promise is in exactly one of three states.
| State | Meaning | Triggered by |
|---|---|---|
| Pending | The work is not done yet | Initial state when the promise is created |
| Fulfilled | The work succeeded | resolve(value) was called |
| Rejected | The work failed | reject(error) was called |
A promise starts pending. It can move to fulfilled or rejected, but never both. Once it settles, it stays settled forever.
You can call then on a settled promise and it runs immediately with the stored value.
const resolved = Promise.resolve("already done");
resolved.then((value) => {
console.log(value); // "already done" (runs immediately)
});Creating a promise with new Promise()
You create a promise by passing a function (the executor) to new Promise(). The executor receives two functions, resolve and reject: call resolve with a value when the work succeeds, and call reject with an error when it fails.
function fetchUser(id) {
return new Promise((resolve, reject) => {
// Simulate an API call
setTimeout(() => {
if (id > 0) {
resolve({ id, name: "Taylor" });
} else {
reject(new Error("Invalid user ID"));
}
}, 500);
});
}The executor runs immediately when the promise is created. The setTimeout call simulates a network delay. After 500ms, the promise either resolves with a user object or rejects with an error.
fetchUser(1)
.then((user) => console.log("Got user:", user.name))
.catch((err) => console.error("Failed:", err.message));
fetchUser(-1)
.then((user) => console.log("Got user:", user.name))
.catch((err) => console.error("Failed:", err.message));The valid ID resolves and logs the user's name. The invalid ID rejects, so its own catch handler runs instead, without throwing an unhandled error.
Got user: Taylor
Failed: Invalid user IDEach call to fetchUser creates a new promise. Promises are one-time; they settle once and never change.
Consuming promises with .then() and .catch()
You do not need to create promises to use them. Most of the time, you consume promises returned by APIs like fetch.
fetch("https://api.example.com/users/1")
.then((response) => response.json())
.then((user) => console.log("User:", user.name))
.catch((error) => console.error("Something went wrong:", error.message))
.finally(() => console.log("Request finished (success or failure)"));.then() runs when the promise fulfills. .catch() runs when it rejects. .finally() runs in both cases, for cleanup.
A key detail: then always returns a new promise. This is what makes chaining possible. The value you return from a then callback becomes the resolved value of the next promise in the chain.
Promise.resolve() and Promise.reject()
These are shortcuts for creating already-settled promises. They are useful for testing and for functions that need to return a promise but already have the value.
// Create a fulfilled promise
const ready = Promise.resolve("Done");
// Create a rejected promise
const failed = Promise.reject(new Error("Something broke"));
ready.then(console.log); // "Done"
failed.catch((err) => console.error(err.message)); // "Something broke"Promise.resolve() also converts thenables (objects with a then method) into standard promises. This is how most promise libraries interoperate.
Promises are microtasks
Promise callbacks run as microtasks, which execute before macrotasks like setTimeout.
console.log("Start");
setTimeout(() => console.log("Timeout"), 0);
Promise.resolve().then(() => console.log("Promise"));
console.log("End");The synchronous lines run first, in order: "Start" then "End". Only after the main script finishes does JavaScript drain the microtask queue, running the promise callback, before finally reaching the timer.
Start
End
Promise
TimeoutThe promise callback runs before the timeout callback, even though both are deferred. Microtasks have higher priority than macrotasks. This matters when you mix promises with timers or event handlers.
Common promise mistakes for beginners
Forgetting that .then() returns a new promise. This is why chaining works and why forgetting to return a value inside a then callback breaks the chain.
// Mistake: not returning from .then()
fetch("/users/1")
.then((res) => {
res.json(); // No return! The next .then() gets undefined
})
.then((data) => {
console.log(data); // undefined
});
// Correct: return the promise
fetch("/users/1")
.then((res) => res.json())
.then((data) => console.log(data));Nesting .then() instead of chaining. If you put a then callback inside another then callback, you lose the flat structure that makes chains easy to read top to bottom.
// Mistake: nested callbacks
fetchUser(1).then((user) => {
fetchPosts(user.id).then((posts) => {
console.log(posts);
});
});
// Correct: chain
fetchUser(1)
.then((user) => fetchPosts(user.id))
.then((posts) => console.log(posts));Not handling rejections. An unhandled promise rejection is a bug. Always add a .catch() or use try...catch with await.
Assuming .then() runs synchronously. Even if a promise is already resolved, then callbacks run asynchronously.
Promise.resolve("value").then(console.log);
console.log("This runs first");
// Output: "This runs first", then "value"Creating a promise that never settles. If you forget to call resolve or reject inside the executor, the promise stays pending forever and any then callback on it never runs.
When to create promises vs consume them
| Create a promise when | Consume a promise when |
|---|---|
| Wrapping a callback-based API | Using fetch, setTimeout wrappers, or any promise-returning function |
| Building a delay/sleep utility | Chaining then and catch calls |
| Implementing a custom async operation | Passing promises to Promise.all, Promise.race, etc. |
Most of the time you consume promises. You only create them when you need to wrap something that does not already return a promise.
For promise chaining and how then values flow through chains, see JavaScript Promise Chaining: A Complete Guide. For the async/await syntax that builds on promises, see JavaScript Async Await Guide: Complete Tutorial.
Rune AI
Key Insights
- A promise is an object representing a future value. It is pending until it settles.
- A promise settles once: either fulfilled with a value or rejected with an error.
- .then() registers a callback for when the promise fulfills.
- .catch() registers a callback for when the promise rejects.
- Promises are the foundation of async/await, fetch, and all modern async JavaScript.
Frequently Asked Questions
What is the difference between a promise and a callback?
Can a promise change state after it is settled?
Do I need to create promises, or can I just use them?
Conclusion
A promise is a placeholder for a value that is not available yet. It has three states (pending, fulfilled, rejected), settles only once, and lets you chain reactions with .then() and .catch(). Understanding promises is the gateway to working with async JavaScript.
More in this topic
Using Decorators for Logging in JS Architecture
Learn how JavaScript decorators wrap class methods to add logging without touching the original code, including setup with Babel and how execution order works.
JavaScript While Loop Explained: A Complete Guide
A while loop repeats code as long as a condition stays true. Learn the syntax, see practical examples, and understand when a while loop is the right choice over a for loop.
JavaScript Loops Tutorial: for, while, do while
Loops let JavaScript repeat code without writing it twice. Learn the three core loop types, what each one does, and when to choose one over another.