What I Wish I Knew as a Beginner About Async/Await

Hey internet! If you've learned any modern JavaScript, you've probably come across the words "asynchronous," "Promises," and "async/await." If you're like me when I first started, these words might have scared you a little, and the code examples looked like magic spells.

But don't worry! There is no magic in async/await This feature is very powerful and makes your job as a developer much easier. This article is all about clearing up these ideas and showing you what I wish I had known from the start.


The Problem: What Asynchronous Code Is

Think about making a cake. You can't put the batter in until the oven is hot. You can't just keep working on other parts of the cake while the oven warms up; you need a sign that it's ready.

  • We have to "wait" for things all the time when we make websites.

  • Waiting for information from an API call

  • Waiting for a file to be opened Waiting for a database query to finish

If our code ran strictly from top to bottom (synchronously), the entire app would freeze every time it had to wait. This would be a bad experience for the user. Asynchronous code lets our program start a long-running task and then move on to other things. It only comes back to the result when it's ready.

The problem is, how do you handle the result when it finally arrives?


The Old Way: A Quick Look at "Callback Hell" Before Promises and async/await, developers used callbacks, which are functions that run when a task is done. The code could get very messy very quickly if you had to do a lot of asynchronous tasks.

This is a simple example, but you can see how it can be hard to read and keep track of. We call this "Callback Hell."

// A simple example
fetchUserData(function(user) {
    if (user) {
        fetchPosts(user.id, function(posts) {
            if (posts) {
                // ... and so on, nesting deeper and deeper
            }
        });
    }
});

It's hard to follow the flow, and error handling becomes a nightmare. There had to be a better way.


The Promise: A Solution to the Mess

The first big step forward was the introduction of Promises. A Promise is an object that represents the eventual completion (or failure) of an asynchronous operation.

To understand Promises, let’s go with the pizza analogy:

  1. You go to a pizza restaurant, place your order, and get a receipt with a ticket number. This ticket number is your Promise, and currently, it is in a pending state.

  2. As time goes by, the pizza is either ready for pickup. In this scenario, the Promise resolves. But in the unfortunate case that the restaurant is out of ingredients, the Promise is rejected.

In programming, this is demonstrated where you add methods .then() and .catch().

// The same task using Promises
fetchUserData()
    .then(user => {
        return fetchPosts(user.id);
    })
    .then(posts => {
        console.log("Here are the posts:", posts);
    })
    .catch(error => {
        console.error("An error occurred:", error);
    });

It is a significant enhancement, to say the least. Code is a lot easier to follow in a step-by-step, sequential manner. Plus, error handling is contained in a single .catch() method.


The Final Form: A Better Way with async/await

The introduction of Promises was a breakthrough in programming. But there was still an untapped opportunity. Sync our asynchronous functions with the feel of synchronous ones.

The purpose of async and await is to provide easier syntax on top of Promises so that they can be in a form that is simpler and user friendly.

async function getPostsForUser() {
    try {
        // 'await' waits for fetchUserData() Promise to resolve
        const user = await fetchUserData();

        // Execution pauses here until we have the user object
        const posts = await fetchPosts(user.id);

        // Execution pauses here until we have the posts array
        console.log("Here are the posts:", posts);

    } catch (error) {
        // Any error in the 'try' block will be caught here
        console.error("An error occurred:", error);
    }
}

// Call the async function
getPostsForUser();

What’s going on in here?

  1. It’s here that we describe the async function we called getPostsForUser.

  2. Inside the function, await is used on fetchUserData(). The code pauses right here until fetchUserData() Promise is resolved, after which the resolved value is assigned to the user variable.

  3. We do the same fetch for fetchPosts() as well.

  4. Notice that the code reads very much like it is synchronous. There is no nesting, no .then() callbacks, just straightforward assignments.

  5. Error Handling is just as straightforward with a familiar try…catch block that captures any rejections from the inner Promise.

What I Learned (And What You Should Probably Ignore)

For me as a novice developer, async/await was a huge difference. It transformed code that was previously unclear and complex into something that was simple and readable. It demystified a multi-step process and made it straightforward.

Remember that async/await do not offer a different approach to handling asynchronous code. It’s much cleaner to write code that relies on Promises. It’s that understanding that dispels all the whole lot of async magic out there.

So, the next time you are programming and you want to write a function that has to wait for something, these two keywords are your best bet. It will be much simpler to read and troubleshoot. Best of luck!

1
Subscribe to my newsletter

Read articles from Hemant Kumar Jha directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Hemant Kumar Jha
Hemant Kumar Jha