A Taste of Syntactic Sugar with Async/await

A Taste of Syntactic Sugar with Async/await

What is syntactic sugar?

In computer science, a syntax or feature designed to make a piece of code easier to express or to read within a programming language is referred to as syntactic sugar.

It makes the language "sweeter" for human use: things can be expressed more clearly, more concisely, or in an alternative style that some may prefer.

Basics of Async/Await

The Async function and the await keyword were introduced with the ES8 (2017) release, as an extension to promises. They were introduced to make promise-based asynchronous programing more readable and easier to perceive. Async/await simplifies the syntax used to consume promise-based APIs.

If you yet to encounter promises or you yet to fully understand working with promises, you should check out my article on Making Promises in JavaScript.

Split into two parts; the async keyword that is appended in front of a function to make sure the function only returns a promise, and the await keyword that can only be used within async functions.

Async functions

Appending the async keyword in front of a function means the function will always return a promise.

//A simple function that returns a value
function echo () {
    return 'Hello World';
}

echo();

Adding the async keyword to the above function will make it return a promise instead of returning the value.

//A async function that returns a promise
async function echo () {
    return 'Hello World';
}

echo();

Declaring async functions

Just like we have used function declaration to declare our echo function example above, we can also declare our async functions using;

Async function expression, as below:

let echo = async function () {
    return 'Hello World';
}

echo();

And we can use arrow functions:

let echo = async () => {
    return 'Hello World';
}

echo();

Working with the returned value when the promise is fulfilled

We can append the .then() method to the promise as we have seen with promise chains, we can use the value to do something else, as seen below:

let echo = async () => {
    return 'Hello World';
}

//appending a .then() method to work with the value
echo().then(response => {
    console.log('I am shouting a big ' + response + '!!')
})

The await keyword

Thing to seriously note: The await keyword can only be used within async functions. The only time it works on its own is when used with Javascript modules.

await can be used to call any asyn promise-based function that returns a promise. It literally pauses the async function block(not the entire code execution) till the promise returns a value.

Basic syntax:

let value = await promise;

A basic use case is as seen below:

async function sampleFunc () {
    let newPromise = new Promise((resolve, reject) => {
        resolve('The block of code is complete.');
    })

    let displayMessage = await newPromise;
    console.log(displayMessage);

}

sampleFunc();

The await can be used in place of the .then() method to handle resolved promises.

//A simple promise to fetch data from the jsonplaceholder
fetch("https://jsonplaceholder.typicode.com/users")
    .then(resp => resp.json())
    .then(result => {
        console.log(result)
    })

Let us try rewriting the above example with async/await:

//Using the async/await syntax
async function getData () {
    let fetchedData = await fetch("https://jsonplaceholder.typicode.com/users")
    let parsedData = await fetchedData.json()
    console.log(parsedData)
}

We can see that it makes our code clearer and easy to read.

Using await with the setTimeout API

async function newFunc () {
    let samplePromise = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('I have been resolved')
        }, 5000)
    });

    let getResolved = await samplePromise;
    console.log(getResolved);
}

newFunc();

Using Async/Await with promise features that handle iterables

Given that async/await built on top promises, they are compatible with all promise features including the likes of Promise.all, Promise.allSettled, Promise.any and Promise.race.

const promise1 = new Promise(resolve => {
    setTimeout(() => resolve('success-1'), 2000)
})

const promise2 = new Promise(resolve => {
    setTimeout(() => resolve('success-2'), 7000)
})

const promise3 = new Promise(resolve => {
    setTimeout(() => resolve('success-3'), 9000)
})

const data = await Promise.all([ promise1, promise2, promise3 ]);
console.log(data)

Using async/await to fetch a set data from an API:

const urls = [
  "https://jsonplaceholder.typicode.com/users",
  "https://jsonplaceholder.typicode.com/posts",
  "https://jsonplaceholder.typicode.com/albums",
];

async function grabData () {
        const [ users, posts, albums ] = await Promise.all(
            urls.map(async function (url) {
                const fetchedData = await fetch(url);
                return fetchedData.json();
            }),
            );
        console.log('Users', users),
        console.log('Posts', posts),
        console.log('Albums', albums)
}

Error handling

When handling errors in async/await there are different paths to go.

One of the more common ways of handling errors with async/await is using the try...catch structure.

Let us try including a try and a catch block to our grabData example above.

// We are grabbing a set of data from the jsonplaceholder and logging it in our browser console
const urls = [
  "https://jsonplaceholder.typicode.com/users",
  "https://jsonplaceholder.typicode.com/posts",
  "https://jsonplaceholder.typicode.com/albums",
];

async function grabData () {
    try {
        const { users, posts, albums } = await Promise.all(
            urls.map(async function (url) {
                const fetchData = await fetch(url);
                return fetchData.json();
            }),
            );
        console.log('Users', users),
        console.log('Posts', posts),
        console.log('Albums', albums)
    } catch (err) {
        console.error(err)
    }
}

Since async/await was create upon promises, you can also follow the hybrid path by chaining the .catch() method instead of the try...catch structure.

const urls = [
  "https://jsonplaceholder.typicode.com/users",
  "https://jsonplaceholder.typicode.com/posts",
  "https://jsonplaceholder.typicode.com/albums",
];

async function grabData () {
        const { users, posts, albums } = await Promise.all(
            urls.map(async function (url) {
                const fetchData = await fetch(url);
                return fetchData.json();
            }),
            );
        console.log('users', users),
        console.log('posts', posts),
        console.log('albums', albums)
}

grabData()
    .catch(err => console.log(err))

Conclusions

My conclusions on using async/await are as follows:

  1. Using async/await makes our asynchronous promise-based API consumption a lot more readable and expressable.

  2. A downside to using async/await is that it makes our code behave synchronously. await literally pauses any code that comes after it till the previous block is done. This can make your program run slower when chaining a set of await blocks.

  3. Using async/await is more a matter of what is comfortable for you and your team.

And that brings us to the end of this article, if you have any form of feedbacks please drop it down in the comments, and if you find this article helpful do drop an appreciation.