How to complete promises in parallel using async and await

July 01, 2017

Back in the bad old days of node.js the asynchronous code handling was done with callbacks, resulting in deeply nested and indented code which could quickly become difficult to follow.

The advent of promises simplifed the chained asynchronus calls to a more managable linear fashion, but there was still a few gotchas for beginners, and complex conditional logic could still be tricky.

The async and await keywords promise even more simplicity in handling asynchronous event, resulting in code written in a synchronous manner which makes it easer to understand and reason.

TypeScript support async/await since version 1.7. However only since version 2.1 has it supported it with an ES5 compile target, meaning we can use it in all of the main browsers today.

A common task is to perform two asynchronous tasks in parallel. Say we need to perform two queries and return the union of the results.

If we were to execute the two queries serially, then the code might look like the following

function union(query1, query2): Promise<[]> {

    var allResults
    
    query1.find().then(results => {
        allResults = results
        return query2.find()
    }).then(results => {
        allResults.push(results)
        return allResults
    }, error => {
        console.error('serial query error', error)
        return Promise.reject(error)
    })
}

The first improvement is actually execute the queries in parallel using Promise.all()

function union(query1, query2): Promise<[]> {

    return Promise.all([query1.find(), query2.find()])
        .then(results => {
            var query1results = results[0]
            var query2results = results[1]
            console.log('query 1 returned ' + query1results.length)
            console.log('query 2 returned ' + query2results.length)
            return query1results.push(query2results)
            
        }, error => {
            console.error('parallel query error', error)
            return Promise.reject(error)
        })
}

Lets see how we would write this with async/await

async function union2(query1, query2): Promise<[]> {

    try {
        const [query1results, query2results] = await Promise.all([query1.find(), query2.find()])
        console.log('query 1 returned ' + query1results.length)
        console.log('query 2 returned ' + query2results.length)
        return query1results.push(query2results)

    } catch(error) {
        console.error('parallel query error', error)
        throw error
    }
}

The first difference is we need to add the async modifier to the function. This is required anytime we want to use the await keyword within a function.

Next using await we can directly assign the asynchronous result to a variable. In this case we are using an ES6 feature called Destructuring assignment where we destructure the returned array into two new const variables.

One difference when using async functions is that even though the return type must be a Promise, within the function we will return the value the promise resolves to. Or where we throw an error, the promise will resolve with that error.

While there isn’t a mind blowing difference in this trivial example, the more complex your asychronous logic, the more readability you will gain using async/await.