Understanding JavaScript Promises: Why, How, and Results

AADI JAIN
3 min readMar 24, 2025

--

JavaScript Promises are an essential feature for handling asynchronous operations efficiently. If you’ve ever dealt with callback hell, you know why Promises are a game-changer. In this blog, we’ll break down Promises using a practical approach: Why we need them, How to use them, and the Result they produce.

Why Use JavaScript Promises?

The Problem: Callback Hell

In JavaScript, asynchronous operations such as fetching data from an API, reading a file, or setting a timeout often use callbacks. However, nesting multiple callbacks can lead to unreadable and hard-to-maintain code, known as callback hell.

Example:

setTimeout(() => {
console.log("First task done");
setTimeout(() => {
console.log("Second task done");
setTimeout(() => {
console.log("Third task done");
}, 1000);
}, 1000);
}, 1000);

This approach quickly becomes difficult to manage.

The Solution: Promises

Promises provide a more structured and readable way to handle asynchronous operations. They allow us to chain operations and handle errors more effectively.

How to Use JavaScript Promises

Creating a Promise

A Promise in JavaScript represents a future value. It can be in one of three states:

  1. Pending — The operation is still in progress.
  2. Resolved (Fulfilled) — The operation completed successfully.
  3. Rejected — The operation failed.

Example:

const myPromise = new Promise((resolve, reject) => {
let success = true;
setTimeout(() => {
if (success) {
resolve("Operation Successful!");
} else {
reject("Something went wrong");
}
}, 2000);
});

Handling Promise Results with .then() and .catch()

To process the result of a Promise, we use .then() for success and .catch() for errors.

Example:

myPromise
.then(result => console.log(result)) // Logs: "Operation Successful!"
.catch(error => console.log(error));

Chaining Promises

One of the biggest advantages of Promises is chaining, which prevents nested callbacks.

Example:

const fetchData = new Promise((resolve) => {
setTimeout(() => {
resolve("Data fetched");
}, 1000);
});
fetchData
.then(data => {
console.log(data);
return "Processing data";
})
.then(processedData => {
console.log(processedData);
return "Saving data";
})
.then(savedData => console.log(savedData))
.catch(error => console.log("Error:", error));

Handling Multiple Promises

Promise.all()

Waits for all promises to resolve.

const promise1 = new Promise(resolve => setTimeout(() => resolve("First done"), 1000));
const promise2 = new Promise(resolve => setTimeout(() => resolve("Second done"), 2000));
const promise3 = new Promise(resolve => setTimeout(() => resolve("Third done"), 3000));
Promise.all([promise1, promise2, promise3])
.then(results => console.log(results))
.catch(error => console.log(error));

Output: ["First done", "Second done", "Third done"]

Promise.race()

Returns the result of the first promise that resolves.

Promise.race([promise1, promise2, promise3])
.then(result => console.log(result));

Output: “First done” (because it resolves first).

Promise.allSettled()

Waits for all promises to settle (either resolved or rejected).

const failingPromise = new Promise((_, reject) => setTimeout(() => reject("Failed!"), 1500));
Promise.allSettled([promise1, failingPromise, promise2])
.then(results => console.log(results));

Each promise result includes status and value or reason.

Promise.any()

If you don’t want to block execution while waiting for all APIs to resolve, but you still need at least one to succeed, Promise.any() is useful. It returns the first fulfilled promise and ignores rejected ones.

Example:

const apiCall1 = new Promise((resolve, reject) => setTimeout(() => reject("API 1 failed"), 1000));
const apiCall2 = new Promise(resolve => setTimeout(() => resolve("API 2 succeeded"), 2000));
const apiCall3 = new Promise(resolve => setTimeout(() => resolve("API 3 succeeded"), 3000));
Promise.any([apiCall1, apiCall2, apiCall3])
.then(result => console.log(result)) // Logs: "API 2 succeeded"
.catch(error => console.log(error));

This way, you ensure at least one API call succeeds without blocking execution.

Result: Clean, Maintainable Async Code

By using Promises, we achieve:

  • Better readability (no callback nesting)
  • Easier error handling
  • More control over async flows

Example: Fetch API with Promises

fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.log("Fetch error:", error));

This is cleaner and more structured than traditional callbacks.

Conclusion

JavaScript Promises revolutionize asynchronous programming by eliminating callback hell and improving code maintainability. By understanding why Promises are needed, how to use them, and the results they produce, you can write cleaner, more efficient async code. Start using Promises today, and you’ll never go back to messy callbacks!

--

--

AADI JAIN
AADI JAIN

Written by AADI JAIN

A learner, who learn things and try to express my learning by writing it down. Trying to be a good engineer :)

No responses yet