Async-Await-ing for your Loving Promises

Zaki Mohammed Zaki Mohammed
June 16, 2021 | 5 min read | 262 Views

It is very difficult to wait on someone's loving promises, but not in JavaScript; thanks to the async/await creamy syntactic sugar made available in JavaScript for Promises. In this article, we will see the evolution of writing promises, from then() and catch() methods to super awesome async/await syntax.

Album Post:

For quick read. Checkout this amazing album!

Async-Await-ing for your Loving Promises
The Dynasty of Async Operations in JavaScript

True love awaits, so do JavaScript promises. Promises provided a lot in JavaScript, bringing back from the callback hell to providing a bunch of useful methods to promise chaining features to exception handling. But still there remains a glitch in your heart, which reminds you that you are actually dealing with an async operation which can’t be a sync one, not even how it looks.

The async/await keyword has taken care of the looks for promises in JavaScript. Now your promises can be awesome and at the same time they can look charming as well. Let us check them out in action in this article.

This article will constitute some of the code logic from the previous 2 articles, one was about the callbacks and other one was about promises. No need to check them out if you're already comfy with JavaScript’s promises.

Before we start with the code, let us set some ground rules for async/await as follows:

  1. Await keyword is only valid in async functions and the top level bodies of modules.
  2. Use try and catch block to handle resolution and rejection of a promise.

The async/await

Without further ado, checkout the below super complex promise example in action, nothing much, just a simple even number finder function shown below:

const isEven = (number) => {
    return new Promise((resolve, reject) => {
        // super complex math problem
        if (number % 2 === 0) {
            resolve();
        } else {
            reject();
        }
    });
};

isEven(4)
    .then(() => console.log("Yay! it's even"))
    .catch(() => console.log("Meh! it's odd"));

Life's been good so far but it can be better with async/await touch. We can simply replace the then and catchy way with await keywords as follows:

await isEven(2);
console.log("Yay! it's even");

The above line will surely give an error since we are having an await without async. So if you are on the top level of your code and want to call a function which returns a promise object and you wanna use the async/await way, simply make use of IIFE (Immediately Invoked Function Expression).

(async _ => {
    await isEven(2);
    console.log("Yay! it's even");
})();

Now the above code will work but not for the odd number, since we are not handling the rejection of the promise, we will face an exception. Let us wrap our await call within a try-catch block as follows:

(async _ => {
    try {
        await isEven(2);
        console.log("Yay! it's even");
    } catch {
        console.log("Meh! it's odd")
    }
})();

There it is, we have now successfully re-written our code as per async/await norms.

Async/await-ing Resolve and Reject

Let us move on and recall an example which is shown in the previous article; where we have written a factorial finder promise based method which takes number as a parameter and returns a promise which is resolved to factorial or an error in case number is not in correct format.

const factorial = (number) => {
    return new Promise((resolve, reject) => {
        if (typeof number === 'number') {
            let fact = 1;
            for (let i = 1; i <= number; i++) {
                fact *= i;
            }
            resolve(fact);
        } else {
            reject('The number provided must be of type number');
        }
    });
};

(async _ => {
    try {
        const result = await factorial(5);
        console.log('Factorial:', result);
    } catch (error) {
        console.log('Error:', error)
    }
})();

Pretty straight forward, and your async code will slowly look like a sync one.

Async/await-ing Promise Methods

You can obviously work with promise methods in async/await way without any hassle. Below we simply created a bunch of promises and used the Promise.all() method to combine these promises to a single unit; by just putting await before the Promise.all() method we can simply wait for all of the async tasks to get completed.

const p1 = new Promise((resolve, reject) => setTimeout(_ => resolve('value 1'), 3000));
const p2 = new Promise((resolve, reject) => setTimeout(_ => resolve('value 2'), 500));
const p3 = new Promise((resolve, reject) => setTimeout(_ => resolve('value 3'), 2000));

const promises = [p1, p2, p3];

(async _ => {
    try {
        const result = await Promise.all(promises);
        console.log('result:', result)
    } catch (error) {
        console.log('error:', error)
    }
})();

// result: [ 'value 1', 'value 2', 'value 3' ]

We can use all of the promise methods (allSetteleted, any, race) likewise.

Async/await-ing Promise Chaining

Promise chaining is already shown in the previous article, so we will pick the same scenario. But first checkout the following async calls:

const db = require('./db')

const fetchUserById = (id) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            const results = db.users.find(i => i.id === id);
            results ?
                resolve(results) :
                reject('Not found');
        }, 500);
    });
};

const fetchPostsByUserId = (userId) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            const results = db.posts.filter(i => i.userId === userId);
            results.length ?
                resolve(results) :
                reject('Not found');
        }, 500);
    });
};

const fetchCommentsByPostId = (postId) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            const results = db.comments.filter(i => i.postId === postId);
            results.length ?
                resolve(results) :
                reject('Not found');
        }, 500);
    });
};

I am bringing the same code from previous article which explains promise chaining of above async calls:

let result;

fetchUserById(1)
    .then(user => {
        result = user;
        return fetchPostsByUserId(user.id);
    })
    .then(posts => {
        result.posts = posts;
        return Promise.all(result.posts.map(i => fetchCommentsByPostId(i.id)));
    })
    .then(comments => {
        result.posts.forEach(post => post.comments = comments.flat().filter(i => i.postId === post.id));
        return result;
    })
    .then(result => console.log('User:', result))
    .catch(error => console.log('Error:', error));

Now let us sugar coat the above code logic with async/await as follows:

(async _ => {
    try {
        const user = await fetchUserById(1);
        const posts = await fetchPostsByUserId(user.id);
        const comments = await Promise.all(posts.map(i => fetchCommentsByPostId(i.id)));

        user.posts = posts.map(post => ({
            ...post,
            comments: comments.flat().filter(i => i.postId === post.id)
        }));
        console.log('User:', user);
    } catch (error) {
        console.log('Error:', error)
    }
})();

Sweet as ice cream, we are first fetching the user followed by the user’s post and then each post’s comments with the help of Promise.all() method. Alas we are flattening the comments array with their respective posts and forming the final user object.

Just for a give away, we need to keep in mind following things while working with async/await:

  1. The await keyword works only within async functions, so if your function is on top level then you can use async IIFE
  2. The try block is for resolved promise while catch is for rejected promise
  3. You can also make use of finally block

Zaki Mohammed
Zaki Mohammed
Learner, developer, coder and an exceptional omelet lover. Knows how to flip arrays or omelet or arrays of omelet.