Why JavaScript Promises are awesome
Tags: promises, javascript, asynchronousprogramming
Published: 2022-08-26 04:34:00

## What is a Promise?
A promise, in computer science, is basically a concept that handles a value that is to be produced in the future, after an asynchronous operation completes successfully or if it does not, gracefully handles a failure.

Too much jargon?

Here’s a high level picture -

You may lend something to a friend and trust them enough to know that you won’t have to stop whatever you were doing and wait for them to return it. You know that they will return it eventually, so, you’ll be able to go one with your life until the item (or an excuse 😅) is returned.
Although you will act on the eventual outcome of the promise that the friend gave you, you will save that for the time the promise is actually fulfilled.

This was the concept in mind when the term Promise (in computer science) was first coined in 1976.

Promise concepts were popularized in the JavaScript world by the jQuery Deferred Objects, that were similar in concept to the Promise, but differed from the current specification of Promises in ECMAScript 2015.

After their official acceptance into the ECMAScript 2015 spec, they are now implemented in all the latest browsers and Node, and are here to cleanup the potential muddle in callbacks and simplify the process.

## What about Callback functions?
Callbacks also produce values after an asynchronous operation but executing one asynchronous operation after the other requires callbacks to be called inside another callback, as shown below.



The callbacks being nested deeper and deeper cause the code to expand horizontally as well, leading to an error-prone and confusing code block understandably known as ‘Callback Hell’.

Promises are used to bypass this.

## Using Promises instead
A promise can be created with its constructor:

```js
let promise = new Promise(function(resolve, reject) {
   // Your application logic goes here
});
```
The function taken into the constructor is the executor function and takes in two arguments: Resolved and Rejected.

The code inside this function is the logic to be executed each time a new Promise is created.

The Promise returned from the constructor should be able to inform the executor function, handling async (asynchronous) operations, when execution has started, completed or returned an error.

## Parts of a promise
A promise has a STATE and a RESULT

A promise has 3 states:

- Pending

- Fulfilled

- Rejected

**Pending** promises in JavaScript, much like in the real world, is a promise that has been executed but not yet completed and can therefore move to the ‘Fulfilled’ or ‘Rejected’ state.

**Fulfilled** promises are resolved or completed, indicating a successful outcome.

**Rejected** promises indicate an unsuccessful outcome due to an error or a timeout.

And, promises that are either Fulfilled or Rejected are called _Settled_.

The result property of a promise can hold the values:

- undefined : When the state is pending

- value : When resolve(value) is called

- error : When reject(error) is called

## Resolving and Rejecting
The promise can be resolved with `resolve()` and placed in the fulfilled state, or rejected with an error as `reject(new Error('Something went wrong'))` in and placed in rejected state.

```js
let myPromise = new Promise(function(resolve, reject) {
   resolve("This will be resolved"); // EXECUTED
   reject(new Error('Something went wrong')); // ignored
   resolve("Resolved again?"); // ignored
});
```

In this example, reject() and resolve() are being called again after the promise has been fulfilled.

**_But, once the state has changed, any further calls to reject and resolve will be ignored._**

## Handling a promise
The handler functions `.then()`, `.catch()`, `.finally()` allow functions consuming the promise to be in sync with the executor function when a promise is fulfilled/rejected.

### Using `.then()`
This is called to handle a promise’s rejection or fulfillment, and can therefore accept up to two functions as arguments:

```js
myPromise.then(
  (result) => { // a successful outcome, promise fulfilled
      console.log(result);
   },
   (error) => { // promise rejected
      console.log(error);
   }
);
```

Or if only one of the two outcomes are needed:

```js
// logs SUCCESFUL outcomes only
myPromise.then(
   (result) => {
      console.log(result);
   }
);
// logs ERROR outcomes only
myPromise.then(
   null,
   (error) => {
      console.log(error);
   }
);
```

### Using `.catch()`
Leaving one argument null in `.then()` to handle errors is not so useful. This is when `.catch()` used.

In the following example `getPromise()` is a user implemented function that accepts a URL and returns a promise stating its outcome.

```js
let urlPromise = getPromise(BAD_URL);
   const consumerFunc = () => {
   promise.catch(error => console.log(error));
}
consumerFunc ();
```

### Using `.finally()`
This handler cleans up after an execution (for example, close a live connection) and will run regardless of whether a promise is rejected or fulfilled.

Here is `.finally()` being used with the other handlers:

```js
let isLoading = true;
isLoading && console.log('Loading...');
//Promise with outcome of URL
promise = getPromise(A_URL);
promise.finally(() => {
   isLoading = false;
   console.log(`Promise settled and loading is ${isLoading}`);
}).then((result) => {
   console.log({result});
}).catch((error) => {
   console.log(error)
});
```

- `.finally()` will set `isLoading` to `false`.

- If the URL is valid, `.then()` is executed and `result` is logged.

- If the URL is invalid, `error` is caught and `.catch()` is executed and `error` is logged.

- `.finally()` will be called regardless of a resolved or rejected promise.

###Chaining Promises
`aPromise.then()` always returns a promise this lets us call `.then()` on the new promise leading to a chain of `.then()` methods passing down a single promise.

A `.then()` can return a new promise, or throw an error too. The following example demonstrates this:

```js
let myPromise = getPromise(URL_RETURNING_MULTIPLE_ITEMS);
promise.then(result => {
   // extracts URL of oneItem
   let oneItem = JSON.parse(result).results[0].url;
   return oneItem; // returns the URL
}).then(oneItemURL => {
   console.log(oneItemURL);
   return getPromise(oneItemURL); // new promise returned
}).then(itemData => {
   console.log(JSON.parse(itemData));
}).catch(error => {
   console.log(‘Error caught. In the Catch block’, error);
});
```
Where the new promise is created, the URL from the previous `.then()` is passed into `getPromise()` which returns the new promise that is then resolved sent down the chain where `itemData` is logged.

If an error is caught, the `.catch()` in the chain is triggered and the error is handled.

And those were the basics of how Promises can be used for asynchronous functions, resulting in clean, understandable code that’s less prone to errors.

**_Till next time, happy coding!_**