The only bad thing about ES7 Async/Await

Multithreaded JavaScript has been published with O'Reilly!
Visit the original version of this article on Code Planet.

I've been using async/await pretty heavily in a side project of mine. It's been pretty awesome to work with and my code has become much more terse while gaining readability! If you read my previous post, The Long Road to Async/Await in JavaScript, you would know that I am a huge fan of the language construct. However, it’s not all rainbows and butterflies.

I noticed this Async Code Example yesterday on GitHub, which I’ve copied and pasted below:

app.get('/', async (req, res, next) => {
    let user = await User.findById(); // assuming .findById() returns a primise
    let org = await Org.findById();
    res.json({
        user: user,
        org: org,
    });
});

This presumedly contrived code does two things; first it locates a user (possibly from a database), then locates an organization (also from somewhere “slow”), returning the two values in the HTTP response. All that without nested callbacks. Hooray! The code to do this ends up being very short and self explanatory.

Can you spot the shortcoming?

The two await statements both result in the function “pausing” while the rest of the application is free to serve requests to other users. However, the Org.findById() statement will not run until the User.findById() statement has completed entirely. Assuming both operations take 100ms to execute, your response time is 200ms!

By applying the easy-to-use async/await pattern to perform work in serial which could have been parallelized, we’ve partially given up that which makes Node.js great; its ability to perform I/O in parallel without blocking the event loop!

If the code was written in the following manner, where the second action is dependent on the result of the first, it would make absolute sense:

app.get('/', async (req, res, next) => {
    let user = await User.findById(); // assuming .findById() returns a primise
    let org = await Org.findById(user.orgId);
    res.json({
        user: user,
        org: org,
    });
});

Notice how we want the second await to occur after the first. This is the perfect situation for using async and await.

The only bad thing about ES7’s async/await is the ease in which we fall into the anti-pattern of serializing work which should be performed in parallel.

How else could this code have been written to be terse, self-descriptive, and run in parallel? Assuming the User and Org objects still provide an API written utilizing promises, one way would be to make use of Promise.all() (see Promise.all() on MDN).

app.get('/', async (req, res, next) => {
    let [user, org] = await Promise.all([
        User.findById(), // assuming .findById() returns a primise
        Org.findById()
    ]);
    res.json({
        user,
        org
    });
});

Of course the code isn’t as eloquent as before, but since Promise.all() returns a promise, we can still await on it. Still assuming each operation takes 100ms to complete, the user should get their result back in 100ms.

Thomas Hunter II Avatar

Thomas has contributed to dozens of enterprise Node.js services and has worked for a company dedicated to securing Node.js. He has spoken at several conferences on Node.js and JavaScript and is an O'Reilly published author.