Skip to Content
Course content
Click on the "Edit" button in the top corner of the screen to edit your slide content.

Imagine you're at a busy burger joint. You place your order at the counter, pay your money, and instead of standing there staring at the cook until the burger is done, the cashier hands you one of those little buzzing pagers.

That pager is a Promise. It’s not the burger itself, but it’s a guarantee that you will eventually receive something. Right now, the pager is in a "pending" state. Eventually, one of two things will happen: the pager buzzes, meaning your food is ready (the promise is fulfilled), or the manager comes over and tells you they've run out of patties (the promise is rejected).

In JavaScript, we use Promises to handle operations that take time—like fetching data from a server or reading a large file—without freezing the entire browser while we wait.

The Agreement and the Outcome

When you create a Promise, you're defining exactly what should happen when the task finishes. You provide an "executor" function that takes two arguments: resolve and reject. I like to think of these as the "success" and "failure" buttons.

const checkInventory = new Promise((resolve, reject) => {
    const stockAvailable = true; // Imagine this is a database call

    setTimeout(() => {
        if (stockAvailable) {
            resolve("Items are in stock!"); // This triggers .then()
        } else {
            reject("Out of stock!"); // This triggers .catch()
        }
    }, 1500);
});

Notice how I used setTimeout? That's just to simulate the delay of a real network request. The key here is that checkInventory is now an object representing a future value. It doesn't hold the string "Items are in stock!" immediately; it holds the promise of that string.

Building a Pipeline of Events

The real power of Promises isn't just waiting for one thing; it's what you do once it arrives. We use .then() to schedule the next step. But here is the part where people often get confused: .then() actually returns a new promise. This allows us to chain operations together like a conveyor belt.

checkInventory
    .then((message) => {
        console.log(message);
        return "Processing payment..."; // Pass this to the next .then()
    })
    .then((paymentStatus) => {
        console.log(paymentStatus);
        return "Order shipped!";
    })
    .then((shippingStatus) => {
        console.log(shippingStatus);
    });

If any of those .then() blocks return a value, that value is wrapped in a resolved promise and passed down the line. If you return another Promise inside a .then(), the chain will wait for that promise to finish before moving to the next step. It's a clean way to avoid the "callback hell" we used to deal with years ago.

When Things Go South

In the real world, APIs fail, servers crash, and users lose internet. If a promise is rejected, it skips all subsequent .then() blocks until it finds a .catch(). I always recommend putting a .catch()` at the very end of your chain to act as a safety net.

fetchUserProfile(userId)
    .then(user => fetchUserPosts(user.id))
    .then(posts => console.log("Got posts:", posts))
    .catch(error => {
        console.error("Something went wrong in the chain:", error);
    })
    .finally(() => {
        console.log("Loading spinner hidden."); // Runs regardless of success or failure
    });

The .finally() block is a lifesaver. I use it almost every time I make an API call to hide a loading spinner or close a database connection, because I don't want to duplicate that logic in both the success and error handlers.

Waiting for the Whole Crew

Sometimes you need several things to happen before you can move forward. Imagine you're building a dashboard that needs data from three different APIs: User Info, Recent Orders, and Notifications. You don't want to wait for them one by one; you want them to run in parallel.

This is where Promise.all() comes in. It takes an array of promises and returns a single promise that resolves only when all of them have succeeded.

const userPromise = fetchUser();
const ordersPromise = fetchOrders();
const notifyPromise = fetchNotifications();

Promise.all([userPromise, ordersPromise, notifyPromise])
    .then(([user, orders, notifications]) => {
        console.log("All data received! Rendering dashboard...");
    })
    .catch(err => {
        console.error("One of the requests failed, so the whole dashboard failed:", err);
    });

Just a heads-up: Promise.all is "all or nothing." If even one promise in that array rejects, the whole thing crashes straight into the .catch(). If you need to know the result of every single promise regardless of whether some failed, you'll want to look into Promise.allSettled(), but for most strict dependencies, Promise.all is your best bet.




📋 Practical Task

Exercise: The Multi-Source Weather Dashboard Aggregator

You are building a weather dashboard that aggregates data from three different mock services. Your goal is to fetch data from all three simultaneously and handle the results correctly.

Requirements:

  • Create three functions: getTemperature(), getHumidity(), and getWindSpeed(). Each should return a Promise that resolves after a random delay (between 500ms and 2000ms) with a mock value (e.g., "22°C", "45%", "12km/h").
  • To make it realistic, make getWindSpeed() randomly fail (reject) 30% of the time.
  • Use Promise.all() to call all three functions.
  • If all three succeed, log a formatted string: "Weather Report: Temp: 22°C, Humidity: 45%, Wind: 12km/h".
  • If any of them fail, log: "Error: Could not retrieve complete weather data."
  • Ensure a final message "Request sequence complete." is logged regardless of the outcome.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.