Javascript Promise

Hello friends, welcome to shrash studio learning, in this article we will learn about one of the most important topics in JavaScript — Promises. If you are preparing for any frontend interview, promises are almost guaranteed to come up. They also make your day-to-day code much cleaner and safer. In this guide we will use simple English, small examples, and short steps. You will learn what a promise is, why it was created, its three states, how to read it, how to chain multiple promises, the mistakes beginners make, and the exact one-line definition to say in an interview.

What Is a Promise

A Promise is a special object in JavaScript that represents the result of an async task that will finish later. That task can be an API call, a file read, a database query, or anything that takes time.

Think of the word promise in real life. If I promise to send you a book tomorrow, you don't have the book right now — but you trust that at some point you will either get the book or hear that I failed. A JavaScript promise works the same way.

When you call an async function that returns a promise, you get back an empty box right away. Later, when the task finishes, JavaScript fills that box with either the result (success) or an error (failure). Your code can attach handlers to this box to run automatically when the data arrives.

Why Were Promises Created

Before promises, developers handled async tasks using callbacks. A callback is just a function you pass to another function to be called later. Callbacks work, but they have two big problems.

Problem 1: Inversion of Control

When you pass a callback to some other API, you give that API full control of your function. You trust that it will call your function exactly once, at the right time. But what if it calls your function twice? What if it never calls it? What if it calls it with wrong data? You have no guarantee.

Problem 2: Callback Hell

When async tasks depend on each other, callbacks nest inside callbacks inside more callbacks. The code starts moving to the right in a pyramid shape — hard to read, hard to debug, hard to change.

Promises solve both problems. They give you back control, they guarantee the callback runs only once, and they let you chain async steps in a clean vertical flow instead of a nested mess.

The Three States of a Promise

A promise is always in one of three states. It starts in one state and changes only once — after that, the state is locked forever.

PROMISE STATES

1. pending

Task is still running
state: "pending"
result: undefined

2. fulfilled

Task finished successfully
state: "fulfilled"
result: your data

3. rejected

Task failed with an error
state: "rejected"
result: an error

Every promise starts as pending. Then it moves to fulfilled or rejected — and that's final. It can never go back. This rule is what makes promises so trustworthy.

Your First Promise: The fetch Function

The simplest way to see a promise in action is the browser's fetch function. It makes an HTTP request and returns a promise.


const userPromise = fetch("https://api.github.com/users/octocat");

console.log(userPromise);
// Promise { state: "pending", result: undefined }

userPromise.then(response => response.json())
           .then(data => console.log(data.name));
// Logs: "The Octocat"  (after a few hundred ms)

On the very next line, the promise is still pending. But within a few milliseconds the request finishes and the promise becomes fulfilled. Your .then handler runs automatically.

How Promises Work: Step by Step

Let's walk through what happens when a promise is used.

Life of a Promise
step 1: You call an async function (like fetch)
step 2: You get a promise object back — state is "pending"
step 3: You attach .then() for success and .catch() for failure
step 4: JavaScript keeps running the rest of your code
step 5: Task finishes in the background (e.g. network reply)
step 6: Promise state becomes "fulfilled" or "rejected"
step 7: The matching handler (.then or .catch) runs automatically

Callbacks vs Promises: A Clear Example

Let's say we have an e-shop with two steps: create an order and start payment. The second step needs the order ID from the first.

The Old Callback Way


const cart = ["shoes", "watch", "bag"];

createOrder(cart, function (orderId) {
  startPayment(orderId, function (paymentId) {
    console.log("Payment done:", paymentId);
  });
});

You hand over your function to createOrder and hope it behaves. You have no guarantee.

The Promise Way


const cart = ["shoes", "watch", "bag"];

createOrder(cart)
  .then(orderId => startPayment(orderId))
  .then(paymentId => console.log("Payment done:", paymentId))
  .catch(err => console.log("Something failed:", err));

Now the control stays with you. You attach your handler to the promise. JavaScript itself guarantees it will run only once, and only at the right time.

Creating Your Own Promise

You can also build a promise yourself using the new Promise constructor. It takes a function with two arguments: resolve (call this on success) and reject (call this on failure).


function wait(ms) {
  return new Promise((resolve, reject) => {
    if (ms < 0) {
      reject("Time cannot be negative");
    } else {
      setTimeout(() => resolve("Done after " + ms + " ms"), ms);
    }
  });
}

wait(1000)
  .then(msg => console.log(msg))
  .catch(err => console.log("Error:", err));
// After 1 second: "Done after 1000 ms"

This small wait function wraps setTimeout in a promise. You can now use .then and .catch on it just like any built-in promise.

Promise Chaining: Escaping Callback Hell

Every .then itself returns a new promise. That means you can line them up one after another in a vertical chain. Each step passes its result to the next.

Real Example: Login → Profile → Posts


loginUser("anita", "pass123")
  .then(user => getProfile(user.id))
  .then(profile => getPosts(profile.username))
  .then(posts => console.log("Latest posts:", posts))
  .catch(err => console.log("Failed at some step:", err));

Read it top to bottom: "Login, then load the profile, then load the posts, then print them — and if anything fails, catch it." One single .catch at the bottom handles errors from any step in the chain.

Important: Always Return the Next Promise

If a step does more async work, you must return that new promise. Otherwise the chain loses the data.


// Wrong: forgot the return
fetch("/user").then(res => {
  res.json().then(data => console.log(data));
});

// Right: return the inner promise
fetch("/user")
  .then(res => res.json())
  .then(data => console.log(data));

Handling Errors with .catch

When a promise is rejected, the closest .catch further down the chain handles it. Any .then in between is skipped.


fetch("https://bad-url-that-does-not-exist.test")
  .then(res => res.json())
  .then(data => console.log(data))
  .catch(err => console.log("Network failed:", err.message));

You can also add a .finally at the end to run code no matter what happens — great for hiding loading spinners.


showSpinner();

fetch("/api/data")
  .then(res => res.json())
  .then(data => render(data))
  .catch(err => showError(err))
  .finally(() => hideSpinner());

Useful Built-in Promise Helpers

Helper What It Does
Promise.all([p1, p2]) Wait for all to succeed; fail fast if any one rejects
Promise.allSettled([p1, p2]) Wait for all to finish — success or failure — and give you the full list
Promise.race([p1, p2]) Finish as soon as the first one finishes (success or failure)
Promise.any([p1, p2]) Finish as soon as the first one succeeds
Promise.resolve(value) Quick promise already fulfilled with a value
Promise.reject(err) Quick promise already rejected with an error

Example: Running Many Requests at Once


const urls = [
  "/api/user",
  "/api/notifications",
  "/api/cart"
];

Promise.all(urls.map(u => fetch(u).then(r => r.json())))
  .then(([user, notifs, cart]) => {
    console.log(user, notifs, cart);
  })
  .catch(err => console.log("At least one failed:", err));

Common Mistakes

Mistake 1: Nesting .then Instead of Chaining

Nesting brings back the callback pyramid. Always flatten the chain.


// Wrong: nested .then
getUser().then(user => {
  getOrders(user.id).then(orders => {
    console.log(orders);
  });
});

// Right: flat chain
getUser()
  .then(user => getOrders(user.id))
  .then(orders => console.log(orders));

Mistake 2: Forgetting .catch

Without a .catch, errors silently disappear and you waste hours debugging. Always end a chain with one.

Mistake 3: Not Returning the Inner Promise

Remember: Inside a .then, if you start another async task, you must return it. Otherwise the next .then runs too early and receives undefined.

Mistake 4: Trying to Change a Resolved Promise

Once a promise is fulfilled or rejected, it stays that way forever. You cannot "reset" it. Calling resolve or reject twice has no effect.

Mistake 5: Mixing Callbacks and Promises

If a function returns a promise, just use .then. Don't also pass it a callback — you will confuse yourself and double-run your code.

Comparison: Callbacks vs Promises vs async/await

Feature Callbacks Promises async/await
Readability Pyramid of doom Clean chain Looks synchronous
Error handling Manual in every callback Single .catch Standard try / catch
Control Given to other function Stays with you Stays with you
Runs callback only once? No guarantee Guaranteed Guaranteed
Parallel work Very hard Promise.all Promise.all + await

async/await is built on top of promises. Once you know promises, async/await is easy to pick up.

Real-World Usage

Loading User Data in a React App


useEffect(() => {
  fetch("/api/profile")
    .then(res => res.json())
    .then(data => setProfile(data))
    .catch(err => setError(err.message))
    .finally(() => setLoading(false));
}, []);

Running Multiple Uploads in Parallel


function uploadFile(file) {
  return fetch("/upload", { method: "POST", body: file });
}

Promise.all(files.map(uploadFile))
  .then(() => console.log("All files uploaded"))
  .catch(err => console.log("At least one upload failed:", err));

Timing Out a Slow Request


const timeout = new Promise((_, reject) =>
  setTimeout(() => reject("Timed out!"), 5000)
);

Promise.race([fetch("/slow-api"), timeout])
  .then(res => console.log("Got it in time"))
  .catch(err => console.log(err));

The Interview Answer

If an interviewer asks "What is a Promise?":

Say this one line — slowly and confidently:

"A Promise is an object that represents the eventual completion (or failure) of an asynchronous operation and its resulting value."

This is the official MDN definition. It sounds professional, covers both success and failure, and matches what interviewers want to hear.
Follow-up tip: After the definition, mention the three states (pending, fulfilled, rejected) and the two main advantages — no inversion of control and no callback hell. That combination will almost always earn you full marks on a promise question.

Summary

A Promise is a JavaScript object that stands in for a value you don't have yet. It starts in the pending state and settles into exactly one of two final states: fulfilled (success) or rejected (failure). Once settled, it never changes again.

Promises were introduced to solve two painful problems with callbacks. They give you back control — you attach your handler instead of passing it away — and they let you chain async steps in a clean vertical flow instead of a nested pyramid.

The three main methods you will use almost every day are .then for success, .catch for errors, and .finally for cleanup. Helpers like Promise.all, Promise.race, Promise.allSettled, and Promise.any handle groups of promises.

Just remember the small rules: always return the inner promise inside a chain, always add a .catch, never try to change a settled promise, and never mix callbacks with promises. Once these habits click, you are ready to move on to async / await — which is just promises wearing a friendlier syntax.

Concept Key Takeaway
Promise Object representing eventual completion of an async task
Three states pending → fulfilled or rejected (locked forever)
.then() Runs when promise is fulfilled
.catch() Runs when promise is rejected
.finally() Runs either way — useful for cleanup
Chaining Each .then returns a new promise — no more nesting
Always return Return the inner promise to keep the chain alive
Promise.all Run many promises in parallel
Inversion of control Callbacks have it, promises fix it
Interview line "Eventual completion of an asynchronous operation"

Chakrapani U

Hi, I’m Chakrapani Upadhyaya, an IT professional with 15+ years of industry experience. Over the years, I have worked on web development, enterprise applications, database systems, and cloud-based solutions. Through this blog, I aim to simplify complex technical concepts and help learners grow from beginners to confident, industry-ready developers.

Previous Post Next Post

نموذج الاتصال