sad puppy waiting
Async and Await are extensions of promises. So if you are not clear about the basics of promises please get comfortable with promises before reading further. You can read my post on Understanding Promises in Javascript.
If you would like to highlights some parts of this article you can use our extension http://bit.ly/highlights-extension. You can search for your previous highlights from the dashboard on https://alpha.app.learningpaths.io/#/highlights
I am sure that many of you would be using async and await already. But I think it deserves a little more attention. Here is a small test : If you can’t spot the problem with the below code then read on.
for (name of ["nkgokul", "BrendanEich", "gaearon"]) {userDetails = await fetch("https://api.github.com/users/" + name);userDetailsJSON = await userDetails.json();console.log("userDetailsJSON", userDetailsJSON);}
We will revisit the above code block later, once we have gone through async await basics. Like always Mozilla docs is your friend. Especially checkout the definitions.
async and await
From MDN
An asynchronous function is a function which operates asynchronously via the event loop, using an implicit
[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise "The Promise object represents the eventual completion (or failure) of an asynchronous operation, and its resulting value.")
to return its result. But the syntax and structure of your code using async functions is much more like using standard synchronous functions.
I wonder who writes these descriptions. They are so concise and well articulated. To break it down.
And MDN goes on to say
An
async
function can contain an[await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await "The await operator is used to wait for a Promise. It can only be used inside an async function.")
expression that pauses the execution of the async function and waits for the passedPromise
's resolution, and then resumes theasync
function's execution and returns the resolved value. Remember, theawait
keyword is only valid insideasync
functions.
Let us jump into code to understand this better. We will reuse the three function we used for understanding promises here as well.
A function that returns a promise which resolves or rejects after n number of seconds.
var promiseTRRARNOSG = (promiseThatResolvesRandomlyAfterRandomNumnberOfSecondsGenerator
= function() {return new Promise(function(resolve, reject) {let randomNumberOfSeconds = getRandomNumber(2, 10);setTimeout(function() {let randomiseResolving = getRandomNumber(1, 10);if (randomiseResolving > 5) {resolve({randomNumberOfSeconds: randomNumberOfSeconds,randomiseResolving: randomiseResolving});} else {reject({randomNumberOfSeconds: randomNumberOfSeconds,randomiseResolving: randomiseResolving});}}, randomNumberOfSeconds * 1000);});});
Two more deterministic functions one which resolve after n seconds and another which rejects after n seconds.
var promiseTRSANSG = (promiseThatResolvesAfterNSecondsGenerator = function(n = 0) {return new Promise(function(resolve, reject) {setTimeout(function() {resolve({resolvedAfterNSeconds: n});}, n * 1000);});});var promiseTRJANSG = (promiseThatRejectsAfterNSecondsGenerator = function(n = 0) {return new Promise(function(resolve, reject) {setTimeout(function() {reject({rejectedAfterNSeconds: n});}, n * 1000);});});
Since all these three functions are returning promises we can also call these functions as asynchronous functions. See we wrote asyn functions even before knowing about them.
If we had to use the function promiseTRSANSG
using standard format of promises we would have written something like this.
var promise1 = promiseTRSANSG(3);promise1.then(function(result) {console.log(result);});
promise1.catch(function(reason) {console.log(reason);});
There is a lot of unnecessary code here like anonymous function just for assigning the handlers. What async await does is it improves the syntax for this which would make seem more like synchronous code. If we had to the same as above in async await format it would be like
result = await promiseTRSANSG(3);console.log(result);
Well that look much more readable than the standard promise syntax. When we used await
the execution of the code was blocked. That is the reason that you had the value of the promise resolution in the variable result
. As you can make out from the above code sample, instead of the .then
part the result is assigned to the variable directly when you use await
You can also make out that the .catch
part is not present here. That is because that is handled using try catch
error handling. So instead of using promiseTRSANS
let us use promiseTRRARNOSG
Since this function can either resolve or reject we need to handle both the scenarios. In the above code we just wrote two lines to give you an easy comparison between the standard format and async await
format. The example in next section gives you a better idea of the format and structure.
async
await
async function testAsync() {for (var i = 0; i < 5; i++) {try {result1 = await promiseTRRARNOSG();console.log("Result 1 ", result1);result2 = await promiseTRRARNOSG();console.log("Result 2 ", result2);} catch (e) {console.log("Error", e);} finally {console.log("This is done");}}}
testAsync();
From the above code example you can make out that instead of using the promise specific error handling we are using the more generic approach of using try
catch
for error handling. So that is one thing less for us to remember and it also improves the overall readability even after considering the try catch block around our code. So based on the level of error handling you need you can add any number of catch
blocks and make the error messages more specific and meaningful.
async await
makes it much more easier to use promises. Developers from synchronous programming background will feel at home while using async
and await
. This should also alert us, as this also means that we are moving towards a more synchronous approach if we don’t keep a watch.
The whole point of javascript/nodejs is to think asynchronous by default and not an after though. async await
generally means you are doing things in sequential way. So make a conscious decision whenever you want to use to async await.
Now let us start analysing the code that I flashed at your face in the beginning.
for (name of ["nkgokul", "BrendanEich", "gaearon"]) {userDetails = await fetch("https://api.github.com/users/" + name);userDetailsJSON = await userDetails.json();console.log("userDetailsJSON", userDetailsJSON);}
This seems like a harmless piece of code that fetches the github details of three users “nkgokul”, “BrendanEich”, “gaearon”
Right. That is true. That is what this function does. But it also has some unintended consequences.
Before diving further into the code let us build a simple timer.
startTime = performance.now(); //Run at the beginning of the code
function executingAt() {return (performance.now() - startTime) / 1000;}
Now we can use executingAt
wherever we want to print the number of seconds that have surpassed since the beginning.
async function fetchUserDetailsWithStats() {i = 0;for (name of ["nkgokul", "BrendanEich", "gaearon"]) {i++;console.log("Starting API call " + i + " at " + executingAt());userDetails = await fetch("https://api.github.com/users/" + name);userDetailsJSON = await userDetails.json();console.log("Finished API call " + i + "at " + executingAt());console.log("userDetailsJSON", userDetailsJSON);}}
Checkout the output of the same.
async-await analysed
As you can find from the output, each of the await function is called after the previous function was completed. We are trying to fetch the details of three different users“nkgokul”, “BrendanEich”, “gaearon”
It is pretty obvious that output of one API call is in noway dependent on the output of the others.
The only dependence we have is these two lines of code.
userDetails = await fetch("https://api.github.com/users/" + name);userDetailsJSON = await userDetails.json();
We can create the userDetailsJSON
object only after getting the userDetails
. Hence it makes sense to use await here that is within the scope of getting the details of a single user. So let us make an async
for getting the details of the single user.
async function fetchSingleUsersDetailsWithStats(name) {console.log("Starting API call for " + name + " at " + executingAt());userDetails = await fetch("https://api.github.com/users/" + name);userDetailsJSON = await userDetails.json();console.log("Finished API call for " + name + " at " + executingAt());return userDetailsJSON;}
Now that the fetchSingleUsersDetailsWithStats
is async we can use this function to fetch the details of the different users in parallel.
async function fetchAllUsersDetailsParallelyWithStats() {let singleUsersDetailsPromises = [];for (name of ["nkgokul", "BrendanEich", "gaearon"]) {let promise = fetchSingleUsersDetailsWithStats(name);console.log("Created Promise for API call of " + name + " at " + executingAt());singleUsersDetailsPromises.push(promise);}console.log("Finished adding all promises at " + executingAt());let allUsersDetails = await Promise.all(singleUsersDetailsPromises);console.log("Got the results for all promises at " + executingAt());console.log(allUsersDetails);}
When you want to run things in parallel, the thumb rule that I follow is
Create a promise for each async call. Add all the promises to an array. Then pass the promises array to Promise.all This in turn returns a single promise for which we can use await
When we put all of this together we get
startTime = performance.now();async function fetchAllUsersDetailsParallelyWithStats() {let singleUsersDetailsPromises = [];for (name of ["nkgokul", "BrendanEich", "gaearon"]) {let promise = fetchSingleUsersDetailsWithStats(name);console.log("Created Promise for API call of " + name + " at " + executingAt());singleUsersDetailsPromises.push(promise);}console.log("Finished adding all promises at " + executingAt());let allUsersDetails = await Promise.all(singleUsersDetailsPromises);console.log("Got the results for all promises at " + executingAt());console.log(allUsersDetails);}
async function fetchSingleUsersDetailsWithStats(name) {console.log("Starting API call for " + name + " at " + executingAt());userDetails = await fetch("https://api.github.com/users/" + name);userDetailsJSON = await userDetails.json();console.log("Finished API call for " + name + " at " + executingAt());return userDetailsJSON;}fetchAllUsersDetailsParallelyWithStats();
The output for this is
Promises run in parallel with timestamps
As you can make out from the output, promise creations are almost instantaneous whereas API calls take some time. We need to stress this as time taken for promises creation and processing is trivial when compared to IO operations. So while choosing a promise library it makes more sense to choose a library that is feature rich and has better dev experience. Since we are using Promise.all
all the API calls were run in parallel. Each API is taking almost 0.88 seconds. But since they are called in parallel we were able to get the results of all API calls in 0.89 seconds.
In most of the scenarios understanding this much should serve us well. You can skip to Thumb Rules section. But if you want to dig deeper read on.
For this let us pretty much limit ourselves to promiseTRSANSG
function. The outcome of this function is more deterministic and will help us identify the differences.
startTime = performance.now();var sequential = async function() {console.log(executingAt());const resolveAfter3seconds = await promiseTRSANSG(3);console.log("resolveAfter3seconds", resolveAfter3seconds);console.log(executingAt());const resolveAfter4seconds = await promiseTRSANSG(4);console.log("resolveAfter4seconds", resolveAfter4seconds);end = executingAt();console.log(end);}sequential();
Sequential Execution
var parallel = async function() {startTime = performance.now();promisesArray = [];console.log(executingAt());promisesArray.push(promiseTRSANSG(3));promisesArray.push(promiseTRSANSG(4));result = await Promise.all(promisesArray);console.log(result);console.log(executingAt());}parallel();
Parallel execution using promises
asynchronous execution starts as soon as the promise
is created. await
just blocks the code within the async
function until the promise is resolved. Let us create a function which will help us clearly understand this.
var concurrent = async function() {startTime = performance.now();const resolveAfter3seconds = promiseTRSANSG(3);console.log("Promise for resolveAfter3seconds created at ", executingAt());const resolveAfter4seconds = promiseTRSANSG(4);console.log("Promise for resolveAfter4seconds created at ", executingAt());
resolveAfter3seconds.then(function(){console.log("resolveAfter3seconds resolved at ", executingAt());});
resolveAfter4seconds.then(function(){console.log("resolveAfter4seconds resolved at ", executingAt());});
console.log(await resolveAfter4seconds);console.log("await resolveAfter4seconds executed at ", executingAt());console.log(await resolveAfter3seconds);console.log("await resolveAfter3seconds executed at ", executingAt());
};concurrent();
Concurrent start and then await
From previous post we know that .then
is even driven. That is .then
is executed as soon as the promise is resolved. So let us use resolveAfter3seconds.then
and resolveAfter4seconds.then
to identify when our promises are actually resolved. From the output we can see that resolveAfter3seconds
is resolved after 3 seconds and resolveAfter4seconds
is executed after 4 seconds. This is as expected.
Now to check how await
affects the execution of code we have used
console.log(await resolveAfter4seconds);console.log(await resolveAfter3seconds);
As we have seen from the output of .then
resolveAfter3seconds
resolved one second before resolveAfter4seconds
. But we have the await
for resolveAfter4seconds
and then followed by await
for resolveAfter3seconds
From the output we can see that though resolveAfter3seconds
was already resolved it got printed only after the output of console.log(await resolveAfter4seconds);
was printed. Which reiterates what we had said earlier. await
only blocks the execution of next lines of code in async
function and doesn’t affect the promise execution.
MDN documentation mentions that Promise.all
is still serial and using .then
is truly parallel. I have not been able to understand the difference and would love to hear back if anybody has figured out their heard around the difference.
Here are a list of thumb rules I use to keep my head sane around using async
and await
async
functions returns a promise.async
functions use an implicit Promise to return its result. Even if you don’t return a promise explicitly async
function makes sure that your code is passed through a promise.await
blocks the code execution within the async
function, of which it(await statement
) is a part.await
statements within a single async
function.async await
make sure to use try catch
for error handling.async
function. By doing this you are making sure that somebody else can use your function asynchronously.await
within loops and iterators. You might fall into the trap of writing sequentially executing code when it could have been easily done in parallel.await
is always for a single promise. If you want to await
multiple promises(Run this promises in parallel) create an array of promises and then pass it to the Promise.all
function.await
only blocks the code execution within the async
function. It only makes sure that next line is executed when the promise
resolves. So if an asynchronous activity has already started then await
will not have an effect on it.Please point out if I am missing something here or if something can be improved.
If you didn’t have anything new to learn from this post and you are from Bangalore then we would like to hire you. We have a opening in our product team. Leave a private message if you would like to know further.
If you liked this article and would like to read similar articles, don’t forget to clap.
Click and drag to clap more than once. 50 is the limit.
This article is a part of my learning series, which I call Understanding X. You can read more above it in this link.
Understanding Javascript
Understanding promises in JavaScript_I have had a kind of “love and hate” relationship with JavaScript. But nevertheless JavaScript was always intriguing…_hackernoon.com
Understanding async-await in Javascript_Async and Await are extensions of promises. So if you are not clear about the basics of promises please get comfortable…_hackernoon.com
Should I use Promises or Async-Await_I recently read a medium post where the author claimed that using async-await is better than using promises. While this…_hackernoon.com
Understanding npm in Nodejs_I think npm was one of the reasons for quick adoption of nodejs. As of writing this article there are close 7,00,000…_hackernoon.com
Understanding Linux
Linux Survival Guide for Beginners_Survival guide for those who are starting with Linux_hackernoon.com
Understanding Zabbix_Monitor all your server infrastructure_hackernoon.com
Understanding UFW_Ubuntu Firewall for humans_hackernoon.com
Linux Systemctl_Monitoring services and starting them on boot_hackernoon.com
Understanding React
Understanding Functional Components_React is all about keeping your front-end code modular and reusable. Components play a crucial role in making React…_hackernoon.com
Understanding Learning
The Thing about Rabbit Holes_The dictionary meaning of a rabbit hole is as follows._medium.com
Understanding Cryptocurrencies
Why comparing cryptocurrency prices is wrong_Price is an important indicator. But it can also be misleading in many cases._blog.goodaudience.com
Beginner’s Guide to “Investing in Cryptocurrencies”_It has been more than an year since I started investing in cryptocurrencies. Following Warren Buffet’s advice of “Never…_hackernoon.com