For some of you, this topic can be very familiar, but I think it is still actual nowadays. I wanted to write this post because we started to use async/awaits in our big project and I have some experience to share.

I don’t want to dive deep into details about generators and other approaches which were predecessors of async/await but I want to show how to easily migrate from Promises to async/awaits in your project and when you should pay attention with async/awaits.

Promise

First of all, let’s remember what is the Promise? As it is written in MDN

Promise is an object which represents the eventual completion (or failure) of an asynchronous operation, and its resulting value.

In other words, Promise is an unknown result of some operation which will be known in the future. Usually, we can meet Promises in JS when we’re working with remote HTTP requests.

async/await

I like a definition that async/await in JS is just a syntax sugar for Promises.

Proposal of this feature exists quite long and it is already on stage 3, so browsers widely support it. If you don’t need to support ancient browsers (or if you’re already using Webpack) you can freely use async/await.

So why async/await is better than Promise? From a syntax side, async functions are really easier to read. But I think this is a slightly wrong question because async/awaits and Promises are different things and we shouldn’t compare them in this way. I believe that async/awaits and Promises should be used together to unleash the power of asynchronous operations and simplicity of code.

Well, let’s look at the Promise-based example of code:

const fetchGitHubReposCount = username =>
    fetch(`https://api.github.com/users/${username}`)
        .then(response => response.json())
        .then(result => result.public_repos);

The function returns Promise which will resolve repositories count of a specified user. How do we usually use such kind of functions?

fetchGitHubReposCount('tc39')
    .then(count => console.log(count));

Looks pretty easy. But what if we need to process this result somehow and send it to another URL? Every time we would need another .then(...) for that. Well, that looks not so nice and we can improve this with async/awaits.

const count = await fetchGitHubReposCount('tc39');
console.log(count);

Better, isn’t it? The code now looks plain and it is still asynchronous. But what if we need to fetch info about two users simultaneously? Probably you’ve already heard about Promise.all() and yes, for async/await you should also use Promise.all() in such cases.

const [countTC39, countMe] = await Promise.all([
    fetchGitHubReposCount('tc39'),
    fetchGitHubReposCount('alexey-detr'),
]);

The example above is a nice example of how async/await can be used with Promise together. This is what I meant speaking that we shouldn’t compare them.

Please note. It is a common mistake when developers forget about simultaneous tasks processing. You shouldn’t blindly write async/await code and expect that it will magically process simultaneous cases. If something can run in parallel you should use Promise.all or other tricky constructions to achieve optimal performance.

async function

Above we used an await keyword only, but every time there was an async prefix which is also usually used in conjunction with function. So what is the async function? First of all, it is a function which handles awaits inside of it and always returns a Promise.

That’s a nice fact about returning a Promise because we can combine old style Promise-based code with a new one and migrate to async/await smoothly.

In a real world, you can’t just write await outside of async function as we did before (it works in browser consoles because they have a special wrapper for that). Let’s rewrite an example above so it would work in a real world.

(async function () {
    const [countTC39, countMe] = await Promise.all([
        fetchGitHubReposCount('tc39'),
        fetchGitHubReposCount('alexey-detr'),
    ]);
})();

The code is wrapped by an IIFE (Immediately Invoked Function Expression). It is necessary to do so when you’re going to use await on the root level of a module. But it wouldn’t be an issue with nested functions, they can be async or not.

So how async functions work

Just a few words to describe a program flow inside of async functions. When a program reaches an await keyword, it interrupts processing of current function and continues it after awaited Promise is resolved or rejected. There is no any special magic, the logic is exactly the same as if you would write it with Promises. Just imagine that all your then(...) constructions turned into awaits.

In the end

So we found out how to change an approach from Promise-based to async/await-based without getting into deep details. But there is also a big domain called an error handling. I’m going to write about errors handling in async/awaits and Promises next time.

That’s it, see you next time.