When async/await operations first introduced devs got hyped up about how things going to be clearer, shorter and faster. But, the problem was we were still writing synchronous code. Like, procedures work in order line by line but this is not how async works. In MDN Docs Async defined as Asynchronous functions operate in a separate order than the rest of the code via the event loop, returning an implicit Promise as its result. So, If you have a long-running task such as Db queries or bulky API calls this is the right way to handle. Yet, using await keyword to resolve for every long-running task can be detrimental. I'm going to demonstrate how to use async effectively. I've used Axios for requests and Performance-now for calculating the execution time.
Suppose we have two APIs one for Pokemon and another for Digimon. From line 7 to 11 we just want to do operations related to pokemon. Now, you may ask: Why did you make Digimon call then, Right? To show the impact of await of course. So, it is okay to put two API calls side by side that is completely okay unless you use await. The question you should always be asking is, "Do I really need data coming from API at the next line?" If, the answer is no then avoid await. Even if you are not going to use Digimon data await will try to resolve it and resolving async operations takes a toll on your program. As you can see at Terminal output this program takes 2539 ms. Now, check this out.
This one takes 282 ms. So the thread is still not blocked but it takes 10 times shorter time to execute. Now, let's iterate over these APIs 50 times.
Approximately 40 seconds. Now without Digimon await.
Without await, it takes 10 seconds. So the difference is 30 seconds that is not something we can unsee. If we increase the number of iterations difference will even greater.
Final Thoughts
As you can see how single await can hinder the performance of your program. Don't think sync when you are programming async. Always ask, "Do I really need that data right now?"
Thanks for reading.
Top comments (27)
This is a great write up of a common problem, I would even go one step further and suggest that for your second example could be improved even more
Since the request to the digimon API doesn’t rely on the pokemon API response, you could trigger them separately and only
await
on the Promise once you need it.Perhaps I'm misunderstanding your suggestion, but you either need to assign the result to a variable or use the Promises' chain.
e.g.
will result in a TypeError.
So you need to write it as
which defeats the purpose (depending on the use case, the Promise may be intended to be used later)
or
Thanks for clarifying it. That was exactly what I was trying to say, If you don't need it that exact moment you do not have to await it. This is one of pitfalls of async/await.
Question. Have you checked the performance using
Promise.all
? While this might not be the use-case. I was thinking that maybe there is a difference in speed of execution if you're going to use Promise.all.I think there would be performance gain using
Promise.all
if you were comparing it to Oğuzhan’s first example.Assuming pokemon API takes 3 seconds to respond and digimon API takes 10 seconds to respond (and that they both always succeed). Oğuzhan’s example would take 13 seconds (the sum of each response) before getting to any pokemon related logic, whereas
Promise.all
would bring that down to 10 seconds (the length of the longest response).However, if you sent them off in parallel as per my example then you can get to the pokemon related logic as soon as thats returned, 3 seconds! And while you’re doing the pokemon logic, the digimon response is still coming back in time for when you need to do digimon logic.
This. But instead of using
Promise.all
, use Promise.allSettled.Promise.all
will reject everything if any one of the Promises fail, whereasallSettled
will return each Promises state so you can handle the resolved and rejected ones appropriately.In some cases
Promise.All
out performsAsync/Await
. In most they are roughly the same.Hmmm, no, I really don't think so. As Simon said above, if you call all Promises at the same time then it will only take as long as the longest one to resolve, rather than waiting for the first, then moving on to the second, etc.
James, you were right. Since
Promise.All
was not the topic of this article but I ran some code to test it here the results.Promise.All with loops:
Promise.All without loops:
Hey! Great writing.
Question though(maybe I missed it) - how can you be completely sure that the Digimon part will be done by the time I need it?
Same as pokemon. Do you really think whenever you put await before your request to resolve it resolves it immediately? It still leaves the function scope and continues to execute other code. Whenever request finishes comes back to the function scope and execute the rest of the code. But, I may be mistaken.
I know that when I await something then the data will be available in the next line.
But I'm missing how, for example in the second picture, I can be sure that Digimon data is available when I read "This is Digimon" part.
You need to
await
it on the Digimon part. Story of this article was to use await keyword when it's really necessary. Why would you want to resolve data withawait
when you are not going to need it?That's the part I was missing from the article and was confused about.
In that case, I agree with what you've said. Thank you for explaining.
I'm glad you deepen the conversation. Thanks.
Maybe I'm being obtuse, but how would you set it to await the Digimon call after it has already been kicked off?
Would that just be like const doSomething = await responseDigimon ?
Actually, not the request itself takes time but resolving it does. Await basically means code is trying to resolve the request via allocating memory to
const doSomething = await responseDigimon
.I get what you're trying to say in the article, but I think the real confusion lies in that why would you do it like this. Why assign assign the axios reference to
responseDigimon
at the top of your function block, but use it much farther down? IMO, theconst
should be grouped with the await so the related logic is together. I'd also much rather break these up into separate functions so you don't get blocking code, but I guess that's another story.I've written down this article because I have seen this type of mistakes numerous time. Imagine a case where you call 5-6 responses with await without really using some of them just to be sure that async call will be in his/her sight so it can be referable some other time. Can you imagine the performance of the app? By the way, as you pointed it out it is really nonsense to call response at the top to use so much later in your code.
Agreed, you should only call the endpoint when you need it. I think some readers just found confusion where one const used an await, but the other didn't and then it was never shown how it was meant to be used later in the code. That's all :)
There are moments though where you would like to be able to load all data at startup. There are a few tricks you can use to await requests on class initiation. In some cases this can be very useful.
You can also chain all requests and just await all the promises with
promise.all
This means the request are all run in async and you dont have to wait for one to finish.I don't really get the point of this, if there's a dependency between pokemon and digimon, you can't just ignore that. Worse, you're just throwing away any possible response from the digimon call, so you don't see the undefineds that are going to come flying out of that loop. All you've shown is that if you skip over something long, it takes less time.
await
only a syntax sugar forthen
what is basically happened here, is a promise chain
this code:
will be translated into this:
this is called a promise chain . Now it's synchronized and blocked
to solve it in parallel you should use this => twitter.com/_nudelx_/status/123739...
Awesome! I never even thought an await may cause performance issues as I know I need the data but I just want to control the order.
great to remember an hopefully an eye-opener for some people as well 👍🏽
How do we make sure that digimon data is received after withAwaitAsyncData finishes working? Assuming we need digimon data somewhere else outside the function.
Maybe a better way would be to make digimon async call out of the withAwaitAsyncData function at all? (and handle its results separately)?
You are right about making digimon call outside of the function scope. But, that's not the context of this article.