Skip to main content

Command Palette

Search for a command to run...

Promises in JavaScript – Handling Asynchronous Operations

Updated
6 min readView as Markdown

Over the past few days, I've been learning how JavaScript communicates with APIs using the Fetch API.

Today, I went deeper into one of the concepts that makes this possible—Promises.

Promises provide a way to handle operations whose results are available sometime in the future.

They are commonly used with API requests, timers, file operations, and many other asynchronous tasks.

Let's dive in.


What is a Promise?

A Promise is an object that represents the eventual completion or failure of an asynchronous operation.

For example:

const promise = new Promise((resolve, reject) => {

    resolve("Operation completed!");

});

A Promise can eventually produce either:

  • A successful result

  • An error


Promise States

A Promise has three possible states:

1. Pending

The operation hasn't finished yet.

Pending

2. Fulfilled

The operation completed successfully.

Fulfilled

3. Rejected

The operation failed.

Rejected

The general flow is:

             ┌──→ Fulfilled
Pending ─────┤
             └──→ Rejected

Once a Promise is fulfilled or rejected, it is settled.


Creating a Promise

We can create our own Promise using the Promise constructor.

const promise = new Promise((resolve, reject) => {

    const success = true;

    if (success) {
        resolve("Success!");
    } else {
        reject("Something went wrong!");
    }

});

Here:

  • resolve() → fulfills the Promise

  • reject() → rejects the Promise


Handling a Promise With then()

The .then() method runs when the Promise is fulfilled.

promise.then((result) => {

    console.log(result);

});

Output:

Success!

Handling Errors With catch()

The .catch() method handles a rejected Promise.

promise
    .then((result) => {
        console.log(result);
    })
    .catch((error) => {
        console.error(error);
    });

This gives us a way to handle failures.


finally()

The .finally() method runs after the Promise settles, regardless of whether it was fulfilled or rejected.

promise
    .then((result) => {
        console.log(result);
    })
    .catch((error) => {
        console.error(error);
    })
    .finally(() => {
        console.log("Operation finished.");
    });

This can be useful for cleanup or stopping a loading indicator.


Promise With setTimeout()

We can simulate an asynchronous operation using setTimeout().

const promise = new Promise((resolve) => {

    setTimeout(() => {
        resolve("Data received!");
    }, 2000);

});

promise.then((data) => {

    console.log(data);

});

The Promise remains pending for approximately two seconds before becoming fulfilled.


Promise Chaining

Promises can be chained together.

Promise.resolve(10)
    .then((number) => {
        return number * 2;
    })
    .then((number) => {
        return number + 5;
    })
    .then((result) => {
        console.log(result);
    });

Output:

25

Each .then() receives the value returned by the previous .then().


Why Promise Chaining is Useful

Promise chaining becomes useful when multiple asynchronous operations depend on one another.

For example:

Request User
     ↓
Get User ID
     ↓
Request User Posts
     ↓
Display Posts

Promises allow us to handle this sequence in an organized way.


Fetch Returns a Promise

One of the most important connections from the last few days is that:

fetch(url)

returns a Promise.

That's why we can write:

fetch("https://jsonplaceholder.typicode.com/users")
    .then((response) => {
        return response.json();
    })
    .then((data) => {
        console.log(data);
    })
    .catch((error) => {
        console.error(error);
    });

The Fetch API and Promises are closely connected.


Async Functions

An async function always returns a Promise.

async function greet() {
    return "Hello!";
}

We can handle its result using .then():

greet().then((message) => {
    console.log(message);
});

Output:

Hello!

The await Keyword

await can be used inside an async function to wait for a Promise to settle.

async function getData() {

    const result = await Promise.resolve("Data received!");

    console.log(result);

}

getData();

await makes asynchronous code easier to read because it allows us to write it in a more sequential style.


async/await With Fetch

Instead of:

fetch(url)
    .then(response => response.json())
    .then(data => console.log(data));

We can write:

async function getUsers() {

    const response = await fetch(
        "https://jsonplaceholder.typicode.com/users"
    );

    const data = await response.json();

    console.log(data);

}

getUsers();

For many developers, this style is easier to read.


Error Handling With try...catch

async/await works very well with try...catch.

async function getUsers() {

    try {

        const response = await fetch(
            "https://jsonplaceholder.typicode.com/users"
        );

        if (!response.ok) {
            throw new Error("Request failed");
        }

        const data = await response.json();

        console.log(data);

    } catch (error) {

        console.error(error);

    }

}

getUsers();

This gives us a clean structure for handling errors.


Promise.all()

Sometimes we need to run multiple independent asynchronous operations.

Promise.all() allows us to wait for multiple Promises.

const promise1 = Promise.resolve("User");
const promise2 = Promise.resolve("Posts");

Promise.all([promise1, promise2])
    .then((results) => {
        console.log(results);
    });

Output:

["User", "Posts"]

The returned Promise fulfills when all of the supplied Promises fulfill.


Why Promise.all() is Useful

Suppose we need:

User Data
Posts
Comments

and none of these requests depends on the others.

We can run them together:

const [users, posts, comments] = await Promise.all([
    fetch("/users"),
    fetch("/posts"),
    fetch("/comments")
]);

This can be more efficient than waiting for each independent request sequentially.


Best Practices

✔ Use async/await when it makes asynchronous code easier to read.

✔ Handle rejected Promises with .catch() or try...catch.

✔ Remember that fetch() returns a Promise.

✔ Check response.ok when using fetch().

✔ Use Promise.all() for independent asynchronous operations when appropriate.

✔ Avoid unnecessarily mixing multiple asynchronous patterns in the same piece of code.


My Biggest Takeaway

Today, I finally understood what is happening behind many of the asynchronous operations I've already been using.

A Promise represents a value that may become available in the future.

I learned how to create Promises, handle them using .then(), .catch(), and .finally(), and work with them more cleanly using async/await.

The connection with the Fetch API is now much clearer:

fetch() → Promise → Response → JSON → Data

2 views

100 Days of Code: My Journey to Becoming a Full Stack Developer

Part 48 of 50

Welcome to my 100 Days of Code journey! In this series, I'll document my daily progress as I learn Full Stack Web Development from the ground up. Every post will cover what I learned, challenges I faced, mistakes I made, and the projects I built. My goal is not just to complete 100 days but to become a better developer through consistency, discipline, and learning in public. Topics I'll cover include: • Git & GitHub • HTML, CSS & JavaScript • React.js • Node.js & Express • MongoDB • APIs • Real-world Projects • AI tools for Developers Whether you're just starting out or revising your fundamentals, I hope this journey helps you learn alongside me. Let's build, learn, and grow together! 🚀

Up next

Promise Methods in JavaScript – Handling Multiple Async Operations

Over the past few days, I've learned about asynchronous JavaScript, Promises, async/await, and the Fetch API. Today, I explored how JavaScript can handle multiple Promises together. Instead of waiting

More from this blog

T

TheSaurceCode

73 posts

Documenting my journey to becoming a Full Stack Developer through daily blogs, coding challenges, projects, tutorials, and lessons learned. Learn, build, and grow with me