JavaScript Promises
Tags: javascript, webdev, programming
Published: 2023-07-04 00:44:51

## Introduction

There are various ways asynchronous operation can be handled or managed, major ones include the use of Callbacks, Promises and Async/Await. In this article, I will be discussing on how you can use JavaScript Promises to handle an asynchronous operations.

JavaScript Promises are an important feature in JavaScript that is used to handle asynchronous operations or tasks that will take some time to complete, such as network requests, database queries or file operations.

JavaScript Promise is one of the best approaches used to handle asynchronous operations in modern JavaScript development because it improves error handling, flow control and readability.

## What is JavaScript Promise

**Analogy:** Let's say your friend has promised to take you out for dinner on a weekend for the way he spoke to you at work. He arranged the venue and time for meetup. There are two things that might happen for the dinner to hold: either your friend makes it to the venue by alerting you, or your friend sends you a message that he couldn't make it with reasons. JavaScript Promise is similar to this scenario.

JavaScript Promise is an **object** that represents the completion or settlement of an asynchronous task.

**Creating Promise**

To create a promise object, you can use the constructor syntax.

```javascript
const myPromise = new Promise((resolve, reject){
    // some operation
});
```

The callback function passed into the constructor is called an ***executor***, which is typically used to perform the asynchronous operation. The *executor* function has two callback functions as the argument: the `resolve()` and `reject()` callback functions, which are provided by JavaScript.

> The `resolve` and `reject` are built-in functions provided by JavaScript when creating a Promise.

If an asynchronous operation runs successfully, the executor will call the `resolve` method and return a **value.**

In case the asynchronous task encounters an issue (error), the executor function will call the `reject` method and return an **error** with a **reason**.

The Promise object returned by the constructor has two internal properties, **state** and **result**.

When a new promise is created, its **state** is `pending` with a **result** of `undefined`. When the asynchronous operation is successful, the **state** is changed to `fulfilled` with a **result** of `value`, and when an error is encountered, the **state** is changed to `rejected` with a **result** of `error`.

A promise can only move from a `pending` state to either being `fulfilled` or `rejected`. Once a promise is `fulfilled` or `rejected`, it cannot go back to another state.

If a promise reaches a `fulfilled` or `rejected` state, the asynchronous operation is said to be *resolved*.


![Promise Object Properties Image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nxtbdmpbbzdqg6kshzfa.PNG)

Let's look at a simple example to show the state and result properties of a Promise object.

```javascript
const counter = true;

const myPromise = new Promise((resolve, reject) => {
// using setTimeout as an async operation
    setTimeout(() => {
        if (counter){
            resolve("Counting...");
        }
        else {
            reject(new Error("Can't Count"));
        }
    }, 3000);  
});
console.log(myPromise);
```

Output

![Pending state image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7bwcra6qbkaqd18nev5q.PNG)

This loads after three seconds:

![Fulfilled state image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hfrj12nxkooiye3mv7qh.PNG)

```javascript
// Changing the counter value to false
const counter = false;
```

Output:

![Failed state](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v5w3oknv0au6ylijek70.PNG)

## Promise Methods

Most of the time you won't be using the `resolve()` and `reject()` methods to handle asynchronous task. JavaScript provides some Promise methods you can use to handle the resolving and rejection of operations.

We will be taking a look at some of the most important fundamental Promise methods JavaScript provides in the subsequent section of this article, such as **then()**, **catch()**, and **finally()**

### then()

`then()` method is one of the core methods of Promise object. It specify what should be done when an asynchronous task succeeds (fulfilled) or fails (rejected).

The main purpose of `then()` is to attach the rejection, and fulfilment handlers to a Promise. Its takes two callback methods, one for fulfilment and the other one for rejection.

**Syntax**

```javascript
promise.then(onFulfilled, onRejected);
```

`onFulfilled` - this is a function that runs when the promise is resolved and returns a result.

`onRejected` - this is a function that runs when the promise is rejected and returns a reason for the rejection.

`then()` will call the `onFulfilled()` if the Promise is resolved successfully or the `then()` will call the `onRejected()` if the Promise is rejected.

```javascript
// Resolving Promise
const pResolved = new Promise((resolve, reject) =>{
    setTimeout(() => resolve("Successful!"), 1000);
})
pResolved.then(result => console.log(result),
                error => console.log(error));
// Output: Successful!

//Rejecting Promise
const pRejected = new Promise((resolve, reject) =>{
    setTimeout(() => reject("Error"), 1000);
});
pRejected.then(result => console.log(result),
                error => console.log(error));
// Output: Error
```

In the example above, the `resolve()` in the `setTimeout()` signifies that the Promise is resolved and will call the necessary argument in the `then()`, which is `onFulfiled()`, the first argument in the `then()` method.

In the second part of the code, the `reject()` method in the `setTimeout()` method signifies that the promise is rejected, thereby calling the second argument in the `then()` method, which is `onRejected()`

The `then()` method can accepts either the `onFulfiled()` or `onRejected()`. Its depend on which promise state we are more interested in.

#### Putting it together:

Now, let's put what we've been discussing into practice by writing some code. You will be building an even numbers generator system. The system will raise an error when encountering an odd number.

```javascript
function getEven(){
    const myPromise = new Promise((resolve, reject) =>{
       setTimeout(()=>{
         const rand = Math.floor(Math.random() * 10);
         if (rand % 2 == 0){
            resolve("Even Number - " + rand);
         }
         else {
            reject(new Error("Invalid " + rand))
         }
       }, 1000);
   });
   myPromise.then(result => console.log(result),
                  error => console.log(error));
}
setInterval(getEven, 2000);
```

Output:

![Even number output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rxk7nkp9ib2qnbs8ldu8.PNG)
The code example demonstrates the creation of a `getEven` function that generates random even numbers asynchronously. It utilizes Promises, `setTimeout` to simulate the asynchronous behavior. The `Math.random` in the code is used to generate numbers. The `then()` method handles the resolving or rejection of the Promise, allowing for the logging of valid even numbers or error messages. The function is executed every two seconds using `setInterval()`.

> Key takeaway from using the `then` method, the `then()` help us to `resolve` or `reject` an asynchronous task.

### catch()

Handling Promise rejection can be done in different ways. One approach is to use the `then()` method's second argument `onRejected()` to handle errors. Another option is to use the `catch()` method, which can be chained to the Promise object. The `catch()` method accepts a callback function to handle the error.

**Syntax**

```javascript
promise.catch(callback(error))
```

```javascript
const run = false;

const myPromise = new Promise((resolve, reject) => {
    setTimeout(()=>{
     if (run){
         resolve("Running...")   
     } 
     else{
         reject(new Error("Error"));
     }  
    }, 1000)
});
myPromise.catch(error => console.log(error)); // Output: Error
```

In the example the `else` statement is executed, thereby executing the `catch()` method handler on the Promise object, the Promise failed and the reason for it failure is stated.

If the `run` variable value is changed to `true` the `if` statement will be executed and the promise will be `resolve` which will make the `catch()` handler not to be executed.

> The `catch()` handler will only be executed if the Promise is rejected or failed.

###

### finally()

Yes! finally, but that's not the end of this article 😃, we still have to clean up our code mess and discuss some few concepts. Let's do some cleanup using `finally()` method.

We might want to execute some functions whether the Promise gets resolved or not. This is where `finally()` method comes into play. The `finally()` method of a Promise object assigns a function to be called when a Promise is settled (either fulfilled or rejected).

The `finally()` method is primarily used to ensure the execution of code that should always run, regardless of the result.

**Syntax**

```javascript
promise.finally(doSomething());
```

> If a `finally()` handler return something, it will be ignored.

```javascript
const counter = true;
const myPromise = new Promise((resolve, reject) =>{
    setTimeout(() =>{
       if (counter){
          resolve("Completed!")      
       }
       else {
         reject("Failed")
       }
    }, 1000);
});
myPromise.then(result => console.log(result))
          .finally(() =>{
              console.log("Do Something!")
           });
// Output: Completed!
// Output: Do Something!
```

In the example above, the Promise is resolved and the `finally()` handler is attached to `then()` handler, and the `finally()` gets executed. The process of attaching a Promise handler with another handler is called PROMISE CHAINING.

## Promise Chaining

We might be in a situation where we need a result of an asynchronous operation in other another one. With the Promise API, we can pass down return result from one handler to another. This method is called PROMISE CHAINING.

When passing down the return result the next handler pick up the result first before moving forward.

**Syntax:**

```javascript
myPromise.then()
           .then()
            .then();
```

```javascript
const myPromise = new Promise((resolve, reject) =>{
    setTimeout( resolve(1), 1000)
})
myPromise.then(result => {
    console.log(result); // 1
    return result;
})
.then((result) =>{
    return result * 2
})
.then((result) =>{
    console.log(result); // 2
    return result * 2;
})
.then((result) =>{
    console.log(result); // 4
    return result * 4;
})
.then((result) =>{
    console.log(result); // 16
});
```

In the example above the return result trickles down the chain and gets used by the next handler in the chain.

## Error Handling in a Promise Chain

When using Promise Chaining a good practice is to handle errors that might occur during promise execution. The most prominent method used for handling chained promise errors is to attach a `catch()` handler to the end the Promise chain.

```javascript
const myPromise = new Promise((resolve, reject) => {
   setTimeout(() => {
        resolve(2)
   });

});

myPpromise.then((result) => {
    console.log(result);    // 2
    return result * 2;
})
.then((result) => {
    console.log(result);    // 4
    return result * 4;     
})
.then((result) => {
    console.log(result);    // 16
    return result * 5;
})
.then((result) => {
    console.log(result);    // 80
})
.catch((error) => {
    console.log(error);    // This handles any error in the chain
}
```

In the example above, if an error is encountered in the promise chain the `catch` block will get implemented, thereby raising an error with a reason. For instance if an error is encountered in the second `then()` block, the error will be caught by the `catch()` and the rest of the `then()` will not be executed.

We might want to handle an error in a specific block and still maintain the results of a Promise chain. The effective approach is to wrap the specific instruction in a try-catch block. By doing so, we handle the error locally without breaking the flow of the Promise chain.

```javascript
const myPromise = new Promise((resolve, reject) =>{
        setTimeout(() => {
            resolve(2);
        })
});

myPromise.then((result) => {
    console.log(result); // 2
    return result * 2;
})
.then((result) => {
    console.log(result); // 4
    try {
         if (result === 4){
            throw new Error("Oops! Something went wrong here")
        }
    }
    catch(error){
        console.log(error); // Oops! Something went wrong here
    }

    return result * 2;
})
.then((result) => {
    console.log(result); // 8
    return result * 2;
})
.then((result) => {
    console.log(result); // 16
})
.catch((error) => {
    throw new Error(error);
})
```

In the example above a try-catch block is used to handle the error that might occur in the second code block and we can still maintain the flow control of the returned result down the chain.

### Putting it together

In this section we will be implementing what we've learned so far. We will be fetching weather data from a weather API and we will be using Promise to handle the asynchronous operation.

On this task, we are going to be making use of a weather API to request for the current weather details of a specific city. You can check their official website [Weather API](https://www.weatherapi.com) and sign-up to obtain your API key.

```javascript
function getData(city){
    const api_key = "your api key";
    const url = `https://api.weatherapi.com/v1/
                    current.json?key=${api_key}&q=${city}`;

    fetch(url)
        .then(response => {
        if (!response.ok){
               throw new Error("Error: " + response.status);
        }
        return response.json();
        });
        .then(data => {
              console.log(data);  
        });
        .catch(error => console.log(error));
}

getData("New York");
```

Output:

![Weather data output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qsiwenlhit0020rs8uoh.PNG)

In the code sample above, the `getData` function takes a `city` parameter input, then we construct the API URL by combining the Weather API endpoint with an API key and a specified `city`. The `fetch` function is used to send an HTTP GET request to the constructed URL, the `fetch` function returns a Promise, which is chained with `.then()` to handle the response. In the first `then()` method we check if the response is successful using the `response.ok` property. If the response is not successful, it throws an error with a status code. The response proceeds with the result to the next `then()` if the response is successful. In the second `then()` block, it parses the response using `response.json` to obtain the weather data, then it logs the retrieved weather data to the console. If any errors occur during the `fetch()` method request or data processing, they are caught and logged in the `catch()` callback. To use the `getData` function and fetch the data for a specific city, you call the `getData` and passing in the name of a city.

### Promises best practices and tips

1. **Handle both resolved and rejected states:** Always handle both the resolved `onfulfilled` and rejected `onRejected` states of a Promise. Use the `then()` method to handle the resolved and `catch()` method to handle the rejected state.
  
2. **Cleanup using `finally()`:** You might want to perform some cleanup operation such as closing connections, or releasing some resources regardless of whether the Promise is fulfilled or rejected. You can use `finally()` method regardless of what the the outcome might be. `finally()` method should not a return a statement.
  
3. **Avoid using Promise constructor unnecessarily:** In most cases it is recommended to use Promise-based API's (e.g., `fetch`, `setTimeout`), Instead of manually constructing a Promise using `new Promise` constructor. This helps to simplify code and reduces the risk of introducing complexity and mistakes.
  
4. **Handle error with `catch()`:** To handle error in asynchronous operation, use the `catch()` to handle any rejected Promises. This allows you to centralize error handling logic in a single location and avoid repetitive error handling code.
  
5. **Remember to return Promises:** When creating a custom function that performs an asynchronous operation that returns a Promise, make sure to return the Promise so that the callers of the function can chain `then()` and `catch()` as needed.
  

## Conclusion

By understanding Promises and their various methods, developers can write more efficient and maintainable code when working with asynchronous tasks.