JavaScript handles a lot of asynchronous work, API calls, timers, and user interactions, and doing all of this with plain callbacks quickly becomes messy and error-prone. JavaScript Promises solve this by giving us a cleaner, more predictable way to manage async operations and avoid callback hell.
In this guide, you will learn what JavaScript Promises are, why they matter, and how they work. We will cover creating JavaScript Promises, chaining them, handling errors, using async/await, and applying practical patterns for running tasks in sequence or in parallel. By the end, you will know exactly how to use JavaScript Promises to write clean, modern asynchronous JavaScript code
Table of Contents:
What Are JavaScript Promises?
Imagine that you’re at a coffee shop. You order a cappuccino and pay at the counter. Rather than stand at the counter, you take a seat, open your laptop, and get to work. After a few minutes, someone calls your name and you get your coffee, and hopefully got some work done during the wait.
This is exactly how JavaScript Promises work. JavaScript runs one task at a time, but Promises let you start an operation (like fetching data) without stopping the rest of your code. When the result is ready, the Promise handles it.
The best part? Promises replaced the messy, headache-inducing “callback hell” that used to plague JavaScript developers. They’re cleaner, easier to read, and pair perfectly with modern async tools like async/await.
How Promises Work Behind the Scenes (Event Loop + Microtask Queue)
When you use then() in JavaScript, it might look like the code runs immediately after the Promise resolves, but actually, JavaScript adds it to a special queue called the microtask queue. The event loop then runs these tasks in order, keeping everything smooth and organized.
Here’s the big picture:
- Call Stack: This is where JavaScript runs synchronous code, line by line.
- Callback (Macrotask) Queue: Things like
setTimeout and DOM events wait here until the stack is clear.
- Microtask Queue: This is where Promise callbacks (
then, catch, finally) go. And here’s the twist:
- The event loop processes all microtasks before moving on to the next macrotask
Promise in JavaScript Example:
Output:
Even though both the Promise and the timeout are scheduled after the synchronous code, the Promise wins because its callback is in the microtask queue, which runs first.
Why this matters:
- If you understand this order, you can write more predictable async code.
- You’ll avoid subtle bugs where you think something should happen later… but it doesn’t.
- You’ll also be able to explain those “weird” timing issues that stump a lot of developers.
Now that you have a fair understanding of how JavaScript promises work, in the next section we will discuss the JavaScript Promise Constructor, which is how you can make a JavaScript Promise yourself without dealing with any pre-determined logic.
How to Create a Promise in JavaScript
The JavaScript Promise constructor is the official way to create new promises in JavaScript. Its syntax looks like this:
let promise = new Promise((resolve, reject) => {
// executor function
});
The JavaScript Promise constructor takes a single argument. An executor function, which itself has two parameters:
resolve(value): used to mark the promise as fulfilled and return a value.
reject(error): used to mark the promise as rejected and return an error.
The moment you create a new JavaScript Promise, the executor runs immediately. This is important to understand because it means your asynchronous task starts right away, not lazily when .then() is attached.
Simple JavaScript Promise Example
Output:
In this JavaScript Promise example, since success is true, the JavaScript Promise resolves and the .then() block runs. If success were false, the .catch() block would execute instead. Let’s move on to a more real-world scenario of using Promises to manage asynchronous operations.
JavaScript Promise Constructor Example: Wrapping setTimeout in a JavaScript Promise
One of the most common uses of the JavaScript Promise constructor is turning callback-based APIs into promises.
Output(After 2 Seconds):
Key Points to Remember about the JavaScript Promise Constructor
- The executor runs right away, not when
.then() is called.
- Only the first call to
resolve or reject matters; extra calls are ignored.
The JavaScript Promise constructor is powerful but often abstracted away by APIs (like fetch) that already return promises. You’ll use it mostly when converting older callback-based code into JavaScript Promises.
Promise Methods: then(), catch(), finally()
So far, we have discussed what a Promise is in JavaScript and how to create one, but unless you consume it, it’s like brewing coffee and never drinking it.
In JavaScript, you “drink” your JavaScript Promise with then(), “handle the bitter taste” with catch(), and “clean the cup” with finally().
1. then()
The code inside the then() keyword in JavaScript is used to store the code that will run once a Promise is resolved.
Output:
You can also chain multiple .then() calls, each of which receives the result from the previous
Output:
2. .catch()
Promises can fail, and when they do, .catch() is your safety net.
Output:
3. finally()
Whether your JavaScript Promise resolves or rejects, .finally() is guaranteed to run. Think of it as turning off the coffee machine, no matter how your morning went.
Output:
4. Chaining with All Three
You can combine then, catch, and finally, for robust async handling:
Output:
- Use
catch() at the end of your chain to handle any errors along the way.
- Keep
finally() for cleanup actions, like hiding loaders, closing connections, or clearing timers.
If you master then, catch, and finally, you’ve already conquered half of async JavaScript. The next step? Learning how to chain and compose multiple JavaScript Promises without getting lost in callback hell.
Promise Combinators: all, allSettled, race, any
In real-world applications, we rarely have promises that work in silos. In fact, most of the time, you’ll need to fetch multiple datasets, run parallel API calls, and process different files, all at the same time! But don’t worry, JavaScript provides Promise combinators (static methods), which act like a traffic controller of sorts.
1. Promise.all()
Runs all JavaScript Promises in parallel and waits until every one of them resolves.
If any of them rejects, the whole thing fails.
Output:
2. Promise.allSettled()
Waits for all Promises to finish, regardless of whether they succeed or fail.
Useful when you care about every result, even the failed ones.
Output:
3. Promise.race()
Returns the result of the first Promise that settles (whether it resolves or rejects).
Output:
4. Promise.any()
Waits for the first Promise to resolve, ignoring rejections until all fail.
If all fail, it throws an AggregateError.
Output:
When to Use Which?
Promise.all(): All must succeed (e.g., load multiple resources together).
Promise.allSettled(): Need the status of each task (e.g., batch processing).
Promise.race(): First one to finish matters (e.g., fastest API mirror).
Promise.any(): You only care about the first successful one.
Async/Await with JavaScript Promises
If JavaScript Promises were a huge leap forward from callback hell, then async/await is the comfy armchair we’ve all been waiting for. It lets you write asynchronous code that looks synchronous, without sacrificing performance.
Instead of chaining .then() calls like you’re building a Lego tower, you can use await to pause until a Promise settles, and then move on.
The result is cleaner, more readable code, which is always good to have.
Basic Example of Async/Await
Output:
- The
async keyword makes the function return a Promise automatically.
- The
await keyword pauses execution until the Promise resolves, then assigns the resolved value to result.
- This eliminates the need for chaining
.then() and improves readability.
Handling Errors with Async/Await
Just like synchronous code uses try...catch, you can wrap await calls inside it to gracefully handle rejections.
Output:
Here, the Promise rejects after 1 second. Instead of using .catch(), we use try...catch to handle errors, keeping everything clean and synchronous-looking.
Async/Await vs. Then Chaining
Let’s compare the two approaches quickly:
Using .then() chaining:
fetchData()
.then((res) => console.log(res))
.catch((err) => console.error(err));
Using async/await:
async function run() {
try {
const res = await fetchData();
console.log(res);
} catch (err) {
console.error(err);
}
}
run();
Both methods achieve the same result, but async/await often feels more intuitive, especially when dealing with multiple asynchronous operations.
JavaScript Promise Real-world Examples
You should by now have a solid understanding of how Promises in JavaScript work, how to create them, and even use async/await to make your code more efficient and readable. The best way to master any concept in JavaScript is to apply it to real-world examples. Below are different examples, starting from the basics and gradually moving into more practical scenarios.
1. Basic JavaScript Promise Example
Output:
Here, we create a JavaScript Promise that either resolves with a success message or rejects with an error. Since success is true, the .then() block runs and prints the result. If you switch success to false, the .catch() block will handle the error.
2. JavaScript Promises Chaining Example
Output:
Chaining lets you run sequential operations where the output of one .then() becomes the input for the next. This is much cleaner than deeply nested callbacks, making code easier to read and maintain.
3. JavaScript Promise Concurrency Example
Output:
Here we use Promise.all() to run two promises at the same time. The result only arrives when both promises finish, which is great for parallel API calls or loading multiple resources together.
Example of JavaScript Promise: Fetching Data with fetch()
<!DOCTYPE html>
<html>
<head>
<title>Promise Example</title>
</head>
<body>
<script>
fetch("https://jsonplaceholder.typicode.com/posts/1")
.then(response => response.json())
.then(data => console.log("Post Title:", data.title))
.catch(error => console.error("Error:", error));
</script>
</body>
</html>
Possible Output:
Post Title: sunt aut facere repellat provident occaecati excepturi optio reprehenderit
This is where JavaScript Promises shine in real-world development. The fetch() API returns a Promise that resolves once the data is received. We can chain .then() calls to parse the JSON and log the result. If something goes wrong (like network issues), .catch() ensures the error is handled gracefully.
JavaScript Promises Cheat Sheet
| Concept | Syntax / Method | Description |
| Creating a Promise | new Promise((resolve, reject) => { ... }); | Defines an async task with success (resolve) or failure (reject). |
| then() | promise.then(onFulfilled) | Handles resolved values. |
| catch() | promise.catch(onRejected) | Handles errors. |
| finally() | promise.finally(callback) | Runs regardless of success or failure. |
| Promise.resolve() | Promise.resolve(value) | Creates a resolved promise. |
| Promise.reject() | Promise.reject(error) | Creates a rejected promise. |
| Promise.all() | Promise.all([p1, p2]) | Resolves when all promises resolve, or rejects if any fail. |
| Promise.allSettled() | Promise.allSettled([p1, p2]) | Resolves after all promises finish, regardless of outcome. |
| Promise.race() | Promise.race([p1, p2]) | Resolves/rejects with the first settled promise. |
| Promise.any() | Promise.any([p1, p2]) | Resolves with the first fulfilled promise; rejects if all fail. |
| Async/Await | async function() { await promise } | Cleaner syntax for working with promises. |
Conclusion
JavaScript Promises simplify asynchronous programming, replacing messy callback patterns with a cleaner, more reliable approach. From handling API calls to running tasks in parallel, JavaScript Promises, along with async/await, are essential for writing modern JavaScript.
If you want to go beyond the basics and build complete applications, our Full Stack Development Course will help you master JavaScript, backend development, databases, and deployment, everything you need to become job-ready.
JavaScript Promises - FAQ
Q1. What are the states of a JavaScript Promise?
A JavaScript Promise has three states:
- Pending: the initial state (neither resolved nor rejected).
- Fulfilled: the operation completed successfully (resolve).
- Rejected: the operation failed (reject).
Q2. What is the difference between Promise and async/await?
- Promise: uses .then() and .catch() to handle results.
- async/await: syntactic sugar that makes asynchronous code look synchronous.
Q3. How do you handle errors in JavaScript Promises?
You can handle errors in Promises using .catch() or try…catch with async/await.
// Promise with .catch()
fetch("/invalid-url")
.then(res => res.json())
.catch(error => console.error("Error:", error));
// async/await with try...catch
async function getData() {
try {
let res = await fetch("/invalid-url");
let data = await res.json();
} catch (err) {
console.error("Error:", err);
}
}
Q4. What is the difference between Promise.all, Promise.race, and Promise.allSettled?
- Promise.all: waits for all Promises to resolve (or rejects if one fails).
- Promise.race: returns the result of the first settled Promise (success or failure).
- Promise.allSettled: waits for all Promises to finish, returning both results and errors.
Q5. Can a JavaScript Promise return multiple values?
No, a Promise in JavaScript resolves with only one value. However, if you need multiple values, you can return them inside an array or an object.