DEV Community

Cover image for Don't make that function async.
José Clovis Ramírez de la Rosa
José Clovis Ramírez de la Rosa

Posted on

Don't make that function async.

Today's a good day to write Javascript code. ES2015 and the newest features that are coming to the language every year makes it a joy to use. With ES2017, Javascript gained Async/Await, which gives a more concise, expressive syntax to handle asynchronous code. It is available in the latest releases of Node and it can be transpiled using Babel to target the browser.

However, as awesome as it may be, marking a function as async won't magically make your code more performant. More often than not, I stumble against this type of code:

async function saveUser(userData) {
  const user = await this.userRepository.saveUserInDatabase(userData);
  return user;
}
Enter fullscreen mode Exit fullscreen mode

Oh, is that type of async function again.

What happens when you await a function.

When using the keyword await, Javascript will pause the function's execution and return the control to it's caller until the asynchronous operation is completed. In the previous example, our saveUser function will stay standby, waiting for the promise returned by the call to the method saveUserInDatabase(userData) to regain the control and return the results.

Now imagine that we make every single function async. Every function would have to wait for every individual function to be resolved, then that function will get back the control, just to give it back to that function's caller. Under this circumstance the Node process will have to start/stop and save/recover every function's execution state. This actually makes the code less performant!

Imagine if we have several layers in our system, each waiting for the other one to complete to temporally gain the control and give it back again. Your callstack might end up looking like this:

Advices for making a function async.

Generally speaking, async functions are just a promise. They represent a future value that has not been computed yet (can also be seen as an ongoing event). It is a good practice to delay the Promise's computation as much as possible until the value is needed. I think that an async function is a good idea if:

  • You need the value of another async function: For example, you may want to wait at the database layer for a query to complete so that you can wrap the result and transform it into an entity of your domain.
  • You need to perform extra steps after the completion of an async function: For example, it would be okay to wait for the user to save in the database if we wanted to send him an email right after. We can return a promise that symbolizes the user email instead.
async function saveUser(userData) {
  const user = await this.userRepository.saveUserInDatabase(userData); // We need the user.
  return this.sendUserEmail(user); // this.sendUserEmail is another async function.
}
Enter fullscreen mode Exit fullscreen mode
  • You need to handle the exception: Sometimes you want to run an async operation but you need to respond to an eventual failure at that layer's level. For example, we could await the database layer execution when creating an user if we have to delete the user profile picture on failure.

  • It makes the code more expressive: There may be a time where we need to make a tradeoff between performance and cleaner code :).

Wrapping up.

Async/Await is one of the best things ever reaching the Javascript world. It is a powerful and expressive way to express asynchronous operations. With all this greatness, overusing it can hurt performance, readability and creates complexity - a complexity that it's just not worth the effort sometimes. When creating functions, it is a good idea to give back the control to the function's caller as fast as possible.

Happy Coding!

Discussion (14)

Collapse
asti profile image
Asti

"Node process will have to start/stop and save/recover every function's execution state"

async/await is just syntactic sugar for Promises, which are essentially a callback abstraction. There's really no concept of starting/stopping.

Collapse
clovis1122 profile image
José Clovis Ramírez de la Rosa Author

Hey Asti,

I'm not sure about async/await being just syntactic sugar for Promises. Sure it was made to make it work with promises easier. If you've worked with Generators before, the concept of "awaiting" a function might look familiar to the concept of "yielding" a result. In both cases, you're giving the control back to the caller. When I say that the function start/stop and save/recover, I refer to this mechanism.

In fact, when you're targeting the browser when there's not much support for async/await syntax, you can use Babel to compile your async/await code to a generator which will yield until all the promises are resolved!. This article explains the concept in a very succinct way - medium.com/siliconwat/how-javascri...

Collapse
asti profile image
Asti

Both yielding and await rewrite the code behind the scenes to a state machine which handles control flow, to behave like co-routines. It's not special in any respect - it could well be user code. There's little overhead other than a few jump tables.

Collapse
nicolasini profile image
Nico S___

I see async/await being used as a way to write "cleaner" code a lot, to make it "flatter". But same deal with promises. We write them to wait for something that is async, but our code doesn't do anything else until is done. We might release the thread to deal with other calls (think of an API), but the current call is being dealt synchronously.
I'm yet to see an example where either promises, callbacks, or async/await are used to wait for some "side effect" but the current execution continues to do something useful while it waits...

Collapse
clovis1122 profile image
José Clovis Ramírez de la Rosa Author • Edited on

Yo Nic,

Async/await looks beautiful when it's done in that way (to make async code look and feel as if it was sync). It makes it easier to deal with.

I think that starting long async functions as early as possible is a great idea. Sadly I don't have any straightforward example about starting a long process while still doing something useful with the current execution. When you do it, it's usually just firing and waiting for other async requests.

Occasionally on the Client, we fire a request to get/save some data, save the request promise in a variable, and perform some small checks before launching an UI update event. At the end we await the request to launch another UI update event with the outcome (success/failure).

Collapse
emkographics profile image
Emko

word up!

Collapse
uniibu profile image
uni

Just to note, Async/await does not block the whole Node interpreter, only the async function it self.

Collapse
hellokyyt profile image
Kyle Harrison

This is precisely what I was looking for, thank you

Collapse
hellokyyt profile image
Kyle Harrison

I know what you're saying, but when it comes time to do "real work", a grand majority of things rely on reading or persisting something before being able to continue on. It's an extremely rare circumstance where taking advantage of having a long running process do it's thing in a separate parallel whiel executing a bunch of other things that don't require it.

Yes, sending an email, you don't need to wait for a response for that. Cool. What else? It's one of the very few "fire and forget" things you can do like collecting analytical data.

One example I can think of, is generating a series of graphs for a page that shows off analytical data. You could spin off the collection of each graphs data into separate threads, and eventually come down to either a Promise.all to tie it all together, or just let each thread independently update the data on the shared main thread state.

But generally speaking? Things are pretty damn sequential. If it werent, we wouldn't have had "callback hell" at all in the first place. Longer running routines are usually doing data manipulation, and usually you can't do anything else until the data is ready to be read.

There's a reason "blocking languages" like PHP had been king for so damn long, it's really good at understanding how on the server side things work.

Client side is a very different beast where many different things can be happening all over the place in parallel. But the server of the web has only one real understanding: a tcp socket has been opened that sends a stateless Request to the server to be parsed, and a Response to send back out through the socket, and close it.

The only time this is different, is if we're taking advantage of manual WebSockets to deal with very different kinds of work, but even still, you're probably going to be waiting to take in the payload, parse the final payload, and basically just execute whatever subroutine you need like any HTTP request, wait for it to complete and then send it back out the socket.

The async / await stuff doesn't need to be on everything, agreed, but with it here, it sure makes this whole process 99% less painful to pick up a project again after months of not seeing it.

Collapse
jacksonelfers profile image
Jackson Elfers

I really prefer anonymous function callbacks. May not be pretty but they're obvious in how they interact with the functions around them. I try to use features that hypothetically older machines can handle even if I'm transpiling es6. If I lose my transpiler for some reason I can easily just make small adjustments to make it compliant for my clients "stone age" computers.

Collapse
acostalima profile image
André Costa Lima

If you are designing an API, functions involving IO operations are typically good candidates to be exposed asynchronously. In some cases, it may be worthwhile to expose both sync and async interfaces.

Collapse
guseyn profile image
Guseyn Ismayylov

The best way to write asynchronous code is use declarative programming, I would suggest Async Tree Pattern

Collapse
chackboom profile image
chackboom

You said "Every function would have to wait for every individual function to be resolved, then that function will get back the control" You are wrong.

Collapse
guseyn profile image
Guseyn Ismayylov

Why I Don't Use Promises and Async/Await Abstractions in Node: guseyn.com/posts/why-i-dont-use-pr...