# Promises in JavaScript – Handling Asynchronous Operations

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:

```javascript
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.

```text
Pending
```

### 2\. Fulfilled

The operation completed successfully.

```text
Fulfilled
```

### 3\. Rejected

The operation failed.

```text
Rejected
```

The general flow is:

```text
             ┌──→ 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.

```javascript
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.

```javascript
promise.then((result) => {

    console.log(result);

});
```

Output:

```text
Success!
```

* * *

# Handling Errors With catch()

The `.catch()` method handles a rejected Promise.

```javascript
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.

```javascript
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()`.

```javascript
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.

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

Output:

```text
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:

```text
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:

```javascript
fetch(url)
```

returns a Promise.

That's why we can write:

```javascript
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.

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

We can handle its result using `.then()`:

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

Output:

```text
Hello!
```

* * *

# The await Keyword

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

```javascript
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:

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

We can write:

```javascript
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`.

```javascript
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.

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

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

Output:

```text
["User", "Posts"]
```

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

* * *

# Why Promise.all() is Useful

Suppose we need:

```text
User Data
Posts
Comments
```

and none of these requests depends on the others.

We can run them together:

```javascript
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**
