Untangle promises with async-await

Writing simple, tractable asynchronous code in JavaScript is fraught with difficulty. It frequently results in a tower of nested callbacks or promises. At Football Radar, we have started experimenting with the proposed async-await syntax, widely expected to land in the language later this year.

The value proposition of async functions is that it will be possible to write asynchronous code in a more procedural, blocking style. This feature is not a new idea: it notably exists in C# and Typescript. Latterly, it has even been emulated in JavaScript via generators and coroutine libraries such as co by TJ Holowaychuk.

To demonstrate the change in coding style when using async-await, I have created a simple example. The function below fetches a list of players for a given game ID. It assumes that we have methods to fetch games, teams and players, all returning promises.

function getPlayersInGame(gameId) {  
    return getGameById(gameId)
        .then((game) => {
            return Promise.all(game.teams.map(getTeamById))
                .then((teams) => Promise.all(
                    teams.reduce((playerIds, team) => {
                        return playerIds.concat(team.players);
                    }, []).map(getPlayerById)
                ));
        });
}

In the promise-ified version, we first fetch the game by ID, then fetch both teams by ID, and finally fetch all players for both teams. Although this is a very simple example, this function suffers from being difficult to read, hard to compose and is not especially easy to reason about.

By contrast, the same function implemented with async-await looks like this:

async function getPlayersInGame(gameId) {  
    const game = await getGameById(gameId);

    const teams = await Promise.all(
        game.teams.map(getTeamById)
    );

    const playerIds = teams.reduce((playerIds, team) => {
        return playerIds.concat(team.players);
    }, []);

    return await Promise.all(
        playerIds.map(getPlayerById)
    );
}

The steps are the same, but readability and tractability are improved, and code style becomes a first-class feature as a result. The signature of these two functions is the same: both take a game ID and return a promise for an array of players, but async-await allows us to express the behaviour in a far more natural way.

At the time of writing, no stable browser or JS runtime currently supports this syntax[1], so getting it to run requires the intervention of a compiler such as Babel - something that will no longer be alien to JS development - and associated libraries such as regenerator. The trade-off is that the compiled code is slightly harder to read and debug, but I contend that this is an acceptable price to pay for radically simplified development.

Async functions are not a silver bullet, but they come closer than any other addition to the language that I have encountered before. Currently, they are only designed to work in tandem with promises and generators, but the expectation is that they will become far more powerful when they are extended to work with other async primitives such as observables and tasks.

Even though the proposal has yet to be included in the Ecma specification and is not broadly supported, it is sufficiently compelling that it is a staple part of all new code that I write, and I encourage you to try it too.

We're hiring!

We're always on the lookout for JavaScript engineers, go here for more information on our roles and how to apply.

[1] It is available behind a flag in the latest version of Microsoft Edge.