5 most important Code Recipes for Asynchronous Code Execution ?

See how to use Promise and its utilities creatively to achieve different kind of use cases like Serial, Parallel, Mixed, Static values as Promise etc.

5 most important Code Recipes for Asynchronous Code Execution ?

Shall the Asynchronous Code batch execute Serially or in Parallel? And how? Knowing quick answers to this question can save you a lot of time.

As in the last post we saw how Asynchronous code execution has been handled with Callbacks, Promises and Async/Await, in this post we will see some code recipes with Promises and Async/Await.

  1. Parallel Promises
  2. Serial Promises
  3. Promise.resolve
  4. Promise.race
  5. Await Promise.all

Parallel Promises

Parallel Promises are helpful in many case. The most useful scenario can be the bootstrapping of the UI/App; where you need to fetch data from different endpoints like User Info, Feed, Messages etc for a Social App.

For this we will use the Promise.all function to fire the requests and then wait for all the promises to finish to do final setup on the UI.

Les see the following code for Social App use case:

const base = 'https://jsonplaceholder.typicode.com';
const updateUserInfo = populateMessages = () => {};
const showUIGuide = buildFeed = () => {};

const getUserInfo = (id) => fetch(`${base}/users/${id}`)
  .then(updateUserInfo);

const getUserMessages = () => fetch(`${base}/comments`)
  .then(populateMessages);

const getFeed = (id) => fetch(`${base}/posts`)
  .then(buildFeed);

const bootstrapRequests = Promise.all([
  getUserInfo(1),
  getUserMessages(),
  getFeed(),
]);

bootstrapRequests.then(values => {
  // show the welcome or guide for UI/App
  showUIGuide();
});

And we see following in the networks panel to actually fire the requests at approximately same time:


Serial Promises

Serial promises are easy to achieve with the Promise Chains. Les see take the example in the parallel promises and do them serial/sequentially:

const base = 'https://jsonplaceholder.typicode.com';
const updateUserInfo = populateMessages = () => {};
const showUIGuide = buildFeed = () => {};

const getUserInfo = (id) => fetch(`${base}/users/${id}`)
const getUserMessages = () => fetch(`${base}/comments`)
const getFeed = (id) => fetch(`${base}/posts`)

const bootstrapRequests = getUserInfo(1)  // get user info
  .then(updateUserInfo)
  .then(getUserMessages) // get user messages
  .then(populateMessages)
  .then(getFeed) // get the social feed for user
  .then(buildFeed);

bootstrapRequests.then(values => {
  // show the welcome or guide for UI/App
  showUIGuide();
});

Promise.resolve

Confused if the value returned is from a promise or not or in other words, it is thennable or not?

Use Promise.resolve and be assured to have .then in the chain.

Like in following example, if browser has fetch API, call fetch API otherwise give an empty response.

const dataRequest = window.fetch
  ? {}
  : fetch(url).then(response => response.json());

dataRequest.then(console.log);

But the problem here is that you can not use the then function on dataRequest directly as if fetch API is not present, it will throw an error.

So with Promise.resolve on the empty object, we can rewrite the above code as follows; which in turn would allow us to call then function on the value, even if it is not returned from the Promise.

const dataRequest = window.fetch
  ? Promise.resolve({})
  : fetch(url).then(response => response.json());

dataRequest.then(console.log);

This way your code will be more testable and reliable to have consistent code flow. Less use of conditional means less chances of confusion.


Promise.race

Have you ever have a situation in your app where you need the fastest available  results, no matter which part of code does that.

And if the code is Asynchronous, it becomes bit tricky to do that.

But with Promise.race you can achieve that. As the name suggest, this function will all a collection of promises to race against time and whichever promise in the collection  is first to respond; no matter resolve or reject; will win the race.

Now if the winner promise has resolved, the success callback in then function will be called with the value.

If the winner promise is rejected, the failure callback of then function will be called with the reason of failure.

Let's see following example:

let result;
const afterSomeTime = time => new Promise(resolve => {
  setTimeout(() => resolve(true), time);
});

result = Promise.race([
	afterSomeTime(100).then(() => '_1'),
	afterSomeTime(110).then(() => '_2'),
]);
result.then(console.log)
// result will have _1

result = Promise.race([
	afterSomeTime(100).then(() => '_1'),
	afterSomeTime(110).then(() => '_2'),
	Promise.resolve('_3')
]);
result.then(console.log)
// result will have _3

result = Promise.race([
	Promise.resolve('_3'),
	'_4',
]);
result.then(console.log)
// result will have _3

result = Promise.race([
	'_5',	
  Promise.resolve('_3'),
	'_4',
]);
result.then(console.log)
// result will have _5

result = Promise.race([]);
result.then(console.log)
// result will be pending forever

But if rejection happens before any other resolve ?, race is still satisfied ?; let's see in following example:

let result;

result = Promise.race([
	new Promise(resolve => {
    setTimeout(() => resolve(true), 100);
  }),
	new Promise((resolve, reject) => {
    setTimeout(() => reject('error'), 50);
  })
]);
result.then(console.log, failures => console.error(failures))

And following output will be shown:


Await Promise.all

As we checked above in the Parallel Promises, Promise.all is used to fire requests in parallels and then wait for all of them to finish.

Though with Async/Await and Promise utility functions, you can have combinations of both and see some interesting use cases, like Await and Promise.all.

Let's take a look at the following code experiments with Promise.all from parallel promises and mixed use of await:

Await all promises/async functions in Promise.all

const bootstrapRequests = Promise.all([
  await getUserInfo(1),
  await getUserMessages(),
  await getFeed(),
]);

And they become sequential

Await first promise/async function in Promise.all and let others execute normally

const bootstrapRequests = Promise.all([
  await getUserInfo(1),
  getUserMessages(),
  getFeed(),
]);

And we have different flow for requests:

Await second promise/async function in Promise.all and let others execute normally

const bootstrapRequests = Promise.all([
  getUserInfo(1),
  await getUserMessages(),
  getFeed(),
]);

And we will have parallel execution for first two promises and last one goes sequential after 2nd one

Await last promise/async function in Promise.all and let others execute normally

const bootstrapRequests = Promise.all([
  await getUserInfo(1),
  await getUserMessages(),
  await getFeed(),
]);

And as you can see that even after waiting for the last one, it is a parallel execution


Conclusion

There are many ways and combinations you can use Promises and Async/Await. But main Idea is to keep the code fast and make amazing non-blocking User Experience.

Let me know through comments ? or on Twitter at @heypankaj_ and/or @time2hack

If you find this article helpful, please share it with others ?

Subscribe to the blog to receive new posts right to your inbox.


Copyrights

Number Icons: https://www.flaticon.com/packs/symbols-25
Cover Photo: https://unsplash.com/photos/IWH9pDNS_iI