DEV Community

Cover image for About async functions

About async functions

Dominik D on July 24, 2021

Async functions are great, especially if you have to call multiple functions in a row that return promises. With async / await, code becomes easier...
Collapse
 
alecvision profile image
alecvision

I thought I had a pretty good handle on async JS, but the 'I don't even...' example at the end has me a little confused. Could you please detail the antipattern you're illustrating there?

I've even been using promises with arrays, async mapping and reducing to my heart's content... but just because it 'works' doesn't mean I necessarily understand the nuances of HOW.

(self taught, JS first language outside of bash)

Collapse
 
alecvision profile image
alecvision

Taking an early guess, is it the combo of async/await and .then()? That doesn't feel quite right tho, because await only works in async functions so I imagine you have to wrap the .... oh wait, my logic only makes sense if the callback inside .then() is also async. The resolved value of the getter is equivalent to the parameter passed to the callback, unless that callback returns a promise (which, in that case, is what you'd be awaiting)

Collapse
 
tkdodo profile image
Dominik D

it's multiple things here really:
1) combining async/await with .then(). I think you'd want to choose one way and then stick to it
2) awaiting the last (and in this case only) Promise is unnecessary. You can just return the Promise, instead of awaiting it and then returning a new Promise due to the nature of an async function.
3) Since the await is unnecessary (see point 2), unless you get rid of the the .then() chain (see point 1), the function being async is also unnecessary.

All in all, that combination is just unnecessarily verbose and shows that whoever has written it doesn't understand what async functions are really doing :)

Collapse
 
alecvision profile image
alecvision • Edited

I'll be doing a lot more reading up on all of this now that I think I'm scraping at what you're saying...

My code has consistently worked up to a recent project - I wrote a function to update state which returns successfully but doesn't produce the desired side-effects. I'm almost certain now it has to do with an unresolved promise somewhere.

I actually scrapped it and chose a different approach, fearing it might be an API bug with the component since I had successfully implemented a similar solution elsewhere in my app.

Thanks for your post and reply - I'm just now getting to the level of confidence to even reach out to other developers, so I really appreciate your thoughtful response!

EDIT: I took one 'await' out of my init function just to see if I was understanding things right - and it still works, but faster by a factor of 3! Thanks again :D

Thread Thread
 
tkdodo profile image
Dominik D

Just saw your edit and that’s incredible 🙌

Collapse
 
thorstenhirsch profile image
Thorsten Hirsch

Actually in your first example I find the then() based approach much more readable than the async/await approach, because the data is being chained or piped through the lines. I also think about it as a more functional way, whilst async/await is the procedural way.

I wouldn't always prefer then() over async/await. The latter is especially more suitable when there's just a single promise.

Collapse
 
tkdodo profile image
Dominik D

Yep, it's definitely a stylistic thing from time to time. To be honest, if you use TypeScript, the types flow through .then chains very nicely as well, so I don't really have a problem with that. I just don't really like that people sometimes stick async on a function for no apparent reason, possibly without knowing what it does and what it's for, so that was my main motivation to write this blog post :)

Collapse
 
mike4040 profile image
Mike Kravtsov

I think important to add that async / await doesn’t work inside Array built in iterator functions, like .forEeach.
What is really bad that this behavior often not mentioned in tutorials, not get caught by linters, and didn’t throw error.
Good news is that there is a way to make it work, but it’s a topic for another article:)

Collapse
 
tkdodo profile image
Dominik D

good point. Am I right in thinking that it works with awaiting Promise.all of the result array, if used with .map instead of with .forEach ?

Collapse
 
leandroandrade profile image
Leandro Andrade

Be careful when using Promise.all because if one Promise fails in the Promises set, the others will still run.

Collapse
 
jcubic profile image
Jakub T. Jankiewicz

I think that your example after "If you make the same function async, it would give you a failed promise." should have async in code. Both examples are the same.

Collapse
 
tkdodo profile image
Dominik D

The next example replaces throw with Promise.reject, both in non-async functions on purpose. The takeaway is that throw is only consistently transformed to failed promises in async functions, which is a detail that is easy to miss. Promise.reject is more explicit and works everywhere.

Collapse
 
jcubic profile image
Jakub T. Jankiewicz

That's right, sorry didn't nice that. I was looking at missing async keyword. Good article.

 
mike4040 profile image
Mike Kravtsov

Or .reduce, starting with Promise.resolve().
The issue is not complicated, easy to find a few good solutions on StackOverflow.

Thread Thread
 
miketalbot profile image
Mike Talbot ⭐ • Edited

Worth just pointing out that for...of and reduce cause the promises to be started in sequence where the all variants start them without waiting for the earlier ones to finish.

My 2 cents: for...of lets you chain promises and terminate early, that's my way to go if that's my need or if the reduce function would be long, as I find that more readable in this circumstance.

Collapse
 
tkdodo profile image
Dominik D

Thank you, happy that you like it :)