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.
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 = '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 = '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 your thoughts and opinions about this article through comments 💬 or on twitter at @patelpankaj and @time2hack.
If you find this article useful, share it with others 🗣