Deal with Promises like a Pro 😎
Tags: javascript, webdev, beginners, tutorial
Published: 2022-01-10 07:47:36

If you've been a JavaScript developer for a while you must have come across Promises. If not, here is a quick intro

> A Promise is a proxy for a value not necessarily known when the promise is created. -- MDN

I've been dealing with promises for a while now, and I think they are a great way to represent asynchronous operations in JavaScript. As great as they are, there is still a lot of functionality they have in-built that most of us don't even know (including myself, until like a week ago).

Through this post today, I would like to explain four interesting built-in features of the Promise object.

Before we dive into it, here are the utilities I used for the demo

```js
// configs for the createPromiseArrayFromConfig function
const allItemsWillResolve = [
  { settleAfterSeconds: 1, shouldReject: false },
  { settleAfterSeconds: 1, shouldReject: false },
];
const someItemsReject = [
  { settleAfterSeconds: 1, shouldReject: false },
  { settleAfterSeconds: 1, shouldReject: true },
  { settleAfterSeconds: 1, shouldReject: false },
];
const allItemsReject = [
  { settleAfterSeconds: 1, shouldReject: true },
  { settleAfterSeconds: 1, shouldReject: true }
];
const itemsWillResolveAtDifferentTime = [
  { settleAfterSeconds: 1, shouldReject: false },
  { settleAfterSeconds: 2, shouldReject: false },
];

// creates an array of promises from the provided config
function createPromiseArrayFromConfig(arrayOfConfigs) {
  // map over the array config objects and return a new Promise for each item as per the config
  return arrayOfConfigs.map(
    ({ settleAfterSeconds, shouldReject }, index) =>
      new Promise((resolve, reject) => {
        // wait "settleAfterSeconds" seconds before settling the promise
        setTimeout(() => {
          if (shouldReject) {
            reject(`Item at ${index} index couldn't resolve! `);
          } else {
            resolve(`Item at ${index} index resolved fine!`);
          }
        }, settleAfterSeconds * 1000);
      })
  );
}
```

As you can guess from the utils, we will run these four scenarios using each of the `Promise` methods and see how each one behaves.

---

## Promise.all()
The `Promise.all` takes an iterable of promises as input and then returns a single promise that'll resolve into an array of the results of the input promises. 

However, the returned promise will reject even if a single promise from the input array rejects. The rejection message/error will be that of the first rejected item.

Let's see its behavior through our examples.

![output of withAll](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3lbroaadhnzb6wmcu1fh.png) 

As we can observe in the image above,
1. the `allItemsWillResolve` config resolves to an array of two string messages.
2. the `someItemsReject` config fails with the second promise rejection as that's the first one to fail.
3. the `allItemsReject` config fails with the first promise rejection as that's the first one to fail.
4. the `itemsWillResolveAtDifferentTime` config takes two seconds to resolve. This is because all the items start resolving at the same time, and the `Promise.all` takes almost the same time to resolve as the longest promise in the array.

> One point to note here is that in some environments like "codesandbox" you might see 3 seconds as output, _(happened with me)_. This can be due to other things running in the app, leaving the `setTimeout` no chance to run later at 2 seconds. I would recommend opening the output in a new tab and see if the output is correct.

### Use case

A good place to use `Promise.all` would be situations like mapping over an array to do **dependent** asynchronous operations and then wrapping the returned array of promises with `Promise.all` call.

Here is a potential use case for `Promise.all`, consider a user "who wishes to zip together all their images from our platform, they don't want partial data, i.e either it's all done or count it as failed."

Here the operations are **dependent** on each other i.e. we only care if all the operations/promises resolve, because even if one of them is missing from zip, "our operation is incomplete". Hence, it would be better to do it using a single `Promise.all` call and show the error to our user if any operation fails.

---

## Promise.allSettled()
The `Promise.allSettled` takes an iterable of promises as the input and returns a single promise that resolves after all of the given promises have either been resolved or rejected, with an array of objects that each describes the outcome of each promise using `value` or `reason`.

Let's see its behavior through our examples.

![output from withAllSettled](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ldurzblsxexoqwctaqqc.png)
 
As we can observe in the image above,
1. the `allItemsWillResolve` config resolves to an array of two objects, each having a `status` and a `value`. 
2. the `someItemsReject` config doesn't reject this time, instead it returns an array of 3 objects, the second of which has a `status` as "rejected" and `reason` as `error message`. It's worth noticing that the second item is missing the key named `value`. 
3. the `allItemsReject` config returns both items with `status` as "rejected".
4. the `itemsWillResolveAtDifferentTime` config takes two seconds to resolve as it works like `Promise.all`.

### Use case

A good use case for `Promise.allSettled`, would be to show our user _(from the `Promise.all` example above)_, a dialog box of which all files couldn't be zipped by looking over the returned data and showing their individual messages. This is a much better user experience compared to the previous one, where we only showed the first error we found.

---

## Promise.any()
The `Promise.any` takes an array of promises as input and returns a single promise that resolves as soon as **one** of the promises in the array fulfills, with the value of the fulfilled promise.

> Promise.any is only available from Node version 15 and above

Let's see its behavior through our examples.

![output from withAny](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l2u0abka9kmfwcqjy30z.png)

As we can observe in the image above,
1. the `allItemsWillResolve` config resolves to the first item's promise.
2. the `someItemsReject` config resolves to the first item's promise.
3. the `allItemsReject` config returns an [AggregateError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AggregateError) as all of the promises are rejected.
4. the `itemsWillResolveAtDifferentTime` config takes one second to resolve because out of the two promises we provided, the first one took only one second to resolve.

### Use case

A good use case for `Promise.any`, would be to request the same resource from multiple sources and show the first one received. Imagine, if you were to connect our customer to the first support assist, the best way to do that would be to request a connection to all of them and pick the one who responded fastest.

---

## Promise.race
The `Promise.race` takes an array of promises as input and returns a single promise that fulfills or rejects as soon as one of the promises in an array fulfills or rejects, with the value or reason from that promise.

Let's see its behavior through our examples.

![output from withRace](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rggpf6jbqrsqtwhjiaxv.png)

As we can observe in the image above,
1. the `allItemsWillResolve` config resolves to the first item's promise.
2. the `someItemsReject` config resolves to the first item's promise.
3. the `allItemsReject` config returns the error from the first promise's rejection.
4. the `itemsWillResolveAtDifferentTime` config takes one second to resolve because out of the two promises we provided, the first one took only one second to resolve.

Some important points
1. If the iterable passed is empty, the promise returned will be forever pending.
2. If the iterable contains one or more non-promise values and/or an already settled promise, then Promise.race will resolve to the first of these values found in the iterable.

> Also, unlike `Promise.any()`, which returns the first fulfilled value, this method returns the first settled (fulfilled or rejected) value. 

### Use case

A good use case for `Promise.race`, would be to set a cutoff timer for operations such that if the given operation doesn't finish in x seconds we throw an error.
```js
// wait for "seconds" before rejecting promise (throws error)
function rejectAfter(seconds) {
  return new Promise((resolve, reject) => {
    setTimeout(() => reject(`Request couldn't resolve in ${seconds}`), seconds * 1000)
  })
}

// this will throw an error if the request doesn't resolve in // 5 seconds
function testCutoff() {
  return Promise.race([testPromise, waitFor(5)])
}
```
---
## Video tutorial and example

You can also watch the [Youtube video]
(https://www.youtube.com/watch?v=1Mc4cFuJ224) to see the examples in action

{% youtube 1Mc4cFuJ224%}


You can also fork and play around with the codesandbox [here](https://codesandbox.io/s/promises-by-anshuman-4v17s).

---

_I hope you enjoyed reading this article as much as I enjoyed writing it!_

For more such content, please follow me on [Twitter](https://twitter.com/sun_anshuman)

> Until next time

<img width="100%" src="https://media.giphy.com/media/kRkJXYahXjSE0/giphy.gif" />

---

## Resources used
I followed [MDN docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) while researching for this post.