Skip to main content

Command Palette

Search for a command to run...

JavaScript Promises Explained for Beginners

Updated
3 min readView as Markdown
JavaScript Promises Explained for Beginners

In the early days of JavaScript, handling tasks that took time—like fetching data from a database or loading an image—meant using callbacks. While functional, they often led to "Callback Hell," a nested mess of code that was impossible to read or debug.

Promises were introduced to clean up this mess. They provide a much more elegant way to handle asynchronous operations by acting as a placeholder for a value that hasn't arrived yet.

1. What Problem do Promises Solve?

The biggest issue with callbacks was the loss of control. You would pass a function into another function and hope it got called correctly.

Promises solve this by returning an object immediately. This object represents the eventual completion (or failure) of an asynchronous operation. Instead of nesting functions, you can "chain" actions together, making your code look much more like a logical sequence of events.

2. The Three States of a Promise

A Promise is always in one of three states. Think of it like ordering a pizza:

  1. Pending: You’ve placed the order, but the pizza hasn't arrived yet. The outcome is still unknown.

  2. Fulfilled (Resolved): The pizza is at your door! The operation completed successfully.

  3. Rejected: The shop called to say they’re out of dough. The operation failed.

Once a promise is either fulfilled or rejected, it is settled, and its state can never change again.

3. Creating a Basic Promise

You create a promise using the new Promise constructor. It takes a function (executor) with two arguments: resolve and reject.

const myPromise = new Promise((resolve, reject) => {
    const success = true;

    if (success) {
        resolve("The operation was a success!");
    } else {
        reject("Something went wrong.");
    }
});

4. Handling Success and Failure

To interact with the value a promise returns, we use .then() for success and .catch() for errors.

  • .then(): Runs when the promise is fulfilled.

  • .catch(): Runs when the promise is rejected.

  • .finally(): Runs no matter what happened (great for stopping loading spinners).

myPromise
    .then((value) => {
        console.log(value); // "The operation was a success!"
    })
    .catch((error) => {
        console.log(error); // "Something went wrong."
    });

5. Promise Chaining

One of the most powerful features of promises is chaining. Since .then() itself returns a new promise, you can pipe data from one asynchronous step to the next.

Imagine a login process:

  1. Verify user credentials.

  2. Then fetch the user's profile.

  3. Then get their latest posts.

verifyUser(credentials)
    .then(user => fetchProfile(user.id))
    .then(profile => fetchPosts(profile.handle))
    .then(posts => console.log(posts))
    .catch(err => console.error("An error occurred in the chain:", err));

This "flat" structure is much easier to manage than nesting three or four callbacks inside each other.

Summary

A Promise is a future value. It’s a guarantee that the code will eventually notify you of success or failure. By mastering promises, you move away from the "spaghetti code" of callbacks and toward a cleaner, more robust way of handling the asynchronous nature of the web.