Learning Javascript Promise Patterns
Tags: javascript, webdev, node, react
Published: 2022-06-09 05:19:20

Hello There!

Let's learn some advance Javascript promise techniques to write more efficient and reliable JS Code :)

---

<u>**1. Fetching multiple independent resources**</u>

consider you have function that fetches a post by ID.

```javascript
const getPostById = async (id) => {
   try {
      // make an async call to fetch the post
      const post = await loadPost(id)
      ...
      return post;
   } catch(err) {
      // handle error
   }
}
```

Now if we have to fetch details of multiple posts:

`const postIds = [1, 2, 3, 4, ...]`

We can do something like this:

```js
const getPosts = (postIds) => {
  const posts = [];
  for(const id of postIds){
    const post = await getPostById(id);
    // do processing
    ...
    posts.push(post);
  }
  return posts;
}
```

Wait a minute! There is a problem here. The problem is `await` keyword will pause the loop until it gets a response from `getPostById()`. Fetching each post by Id is an independent operation and result of multiple requests doesn't depend on each other's response. It doesn't make much sense to wait to fetch next post only after previous post has been fetched.

Let's talk how to resolve this issue. What we can do is make multiple requests concurrently and wait for all of them to get fetched or resolved.

Javascript provides two promise APIs to handle multiple requests concurrently:
 
`Promise.all(...)` and `Promise.allSettled(...)`

<u>Using `Promise.all(...)`</u>


```javascript
const getPosts = (postIds) => {
  try {
    const postPromises = postIds.map(id => getPostById(id));
    const posts = await Promise.all(postPromises);
   // do processing
   ...
    return posts;
  } catch(err) {
     // handle error
  }
}
```

Now, good thing is we are not waiting for previous post request to finish to make request for next one instead now concurrent requests will be fired independent of each other and we are waiting until all posts has been fetched. But there is still one issue here. If one of the promises rejects, `Promise.all(...)` immediately rejects, causing every other post not to load. We can improvise it by using `Promise.allSettled(...)`.

`Promise.allSettled(...)` returns a pending promise that resolves when all of the given promises have been settled either resolved or rejected. This behaviour is very useful to track multiple tasks that are not dependent on one another to complete.

```javascript
const getPosts = (postIds) => {
  const postPromises = postIds.map(id => getPostById(id));
  const posts = await Promise.allSettled(postPromises);
    // outcome of each promise has a status property.
    // If success, it will have value property
    // If fails, it will have reason property
    return posts.reduce((result, post) => {
      if(post.status === 'fulfilled') {
        result.successfullyFetchedPosts.push(post.value)
      } else {
        result.failedPosts.push(post.reason)
     }
      return result;
    }, {
     successfullyFetchedPosts: [],
     failedPosts: [],
    })
}

// using this function
const {
  successfullyFetchedPosts, 
  failedPosts
} = await getPosts([...]);
...
```

Promise returned by `Promise.allSettled(...)` will almost always be fulfilled. The promise will only reject if we pass a value that is not iterable.

---


<u>**2. Avoiding single point of failure using `Promise.any(...)`**</u>

Sometimes, we have to fetch some critical resource like financial market data from external APIs. If the API is down, the app will stop working. The `Promise.any(...)` is extremely useful in this regard. It enables us to request data from multiple sources ( APIs) and use the result of the first successful promise.

`Promise.any(...)` returns a pending promise that resolves asynchronously as soon as one of the promises in the given iterable fulfils.

```js
 const promises = [
    Promise.reject(),
    Promise.resolve(5),
    Promise.reject(),
 ];
 Promise.any(promises).then(console.log) // 5
```

Consider we have three APIs to fetch a resource. We can use `Promise.any(...)` like this:

```js
const apis = [
  'https://api1/resource/10',
  'https://api2/resource/10',
  'https://api3/resource/10'
];

const fetchData = async api => {
  const response = await fetch(api);
  return response.ok ? response.json() : Promise.reject('some error');
}

const getResource = () => Promise.any(
  apis.map(api => fetchData(api))
);

getResource().then(response => {
  // process response
})

```
`Promise.any(...)` allows you to improve the performance of critical applications by using the data from the API that responds first. Also it allows you to improve reliability of the application as even if one of the APIs fails, it will continue working as expected. `Promise.any(...)` will only reject when all the promises passed as the argument reject.

---


<u>**3. Enforcing a time limit for async operations using `Promise.race(...)`**
</u>

Suppose we are fetching some resource from an external API. User Interface will be in the loading state until we get response from the API. Sometimes, APIs take a lot of time to give response back to the client and user will be waiting for it , looking at the loading spinner like forever. A better user experience would be to timeout the request after a given number of milliseconds and show error in the UI like request time out. We can easily do this using `Promise.race(...)`.


`Promise.race(...)` is somewhat similar to `Promise.any(...) as both get settled whenever first promise in the iterable settles`.

`Promise.race(...)` settles as soon as one of the promises rejects. . 

`Promise.any(...)` resolves as soon as one of the promises fulfils. 

`Promise.race(...)` rejects if the first promise  that settles is rejected while `Promise.any(...)` rejects when all the given promises reject.

let's implement timeout of the request:

```js
 const getData = async () => {
    const TIMEOUT_MS = 2000; // IN MILLISECONDS

    const request = fetch(API_URL); // actual request

    const timeout = new Promise((_, reject) => 
      setTimeout(() => reject(new Error('Request time out!')), TIMEOUT_MS)); // rejects after 2000 milliseconds

    return Promise.race([request, timeout];
 }
```

<u>**4. Batching async requests using `Promise.race(...)`**</u>


An interesting use case of `Promise.race(...)` is to batch async request. Here is the simple implementation:

```js
/**
 * 
 * @param {{limit: number, concurrentBatches: number}} batchOptions 
 */
const performRequestBatching = async batchOptions => {

    const query = {
        offset: 0,
        limit: batchOptions.limit
    };
    let batch = [];
    let promises = [];
    do {
        batch = await model.findAll(query);
        query.offset += batchOptions.limit;
        if (batch.length) {
            const promise = performLongRequestForBatch(batch).then(() => {
                // remove the promise from promises list once it is resolved
                promises = promises.filter(p => p !== promise);
            });
            promises.push(promise);
            // if promises length is greater than provided max concurrent batches
            if (promise.length >= batchOptions.concurrentBatches) {
                // then wait for any promise to get resolved
                await Promise.race(promises);
            }
        }
    } while (batch.length)
    // wait for remaining batches to finish
    return Promise.all(promises);
}

// using batching
batchRequest({
  limit: 100,
  concurrentBatches: 5,
})
```

---

**Conclusion**

Hurray! That's it. We have learnt multiple patterns related to Javascript promise api like handling multiple request efficiently using `Promise.all` and `Promise.allSettled`, avoiding SPOF using `Promise.any`, timing out async request for better user experience and batching multiple requests using `Promise.race`.

Please like the post if you have learnt something new :). Also Feel free to point-out or provide suggestions in the comment section if there is some mistake in the post.

Happy Coding !

See you!