Async Programming Like a Pro

Async Programming Like a Pro

Gain an intuitive understanding about how Asynchronous Programming works under the hood...

ยท

7 min read

See, we all have to wait for different things in our day-to-day lives. For instance, it can be waiting for a pizza order to be delivered. While waiting in such cases, we do other tasks to make the most of our time.

This is pretty much the same for computers. Asynchronous programming is how computer programs switch their focus to accomplish other tasks while they wait for something else to be finished.

In this article, we'll explore how this works under the hood.

๐Ÿค” What Does Async Mean?

There are 2 ways for a program to be executed,

  1. Synchronously - Executes line by line in the given order

  2. Asynchronously - Jumping around and executing the program in a way that minimises the runtime (the amount of time it takes to be executed).

The problem with synchronous execution is that when faced with waiting for a third-party API request, the rest of the program gets paused. So, the user goes like: Seriously, WTF is this?

That's where Async comes in to save fellow programmers like us...

๐Ÿ› ๏ธ How Does Async Programming Work?

๐Ÿ”„ The Event Loop

No exaggeration, when you understand this, asynchronous programming becomes a piece of cake. That's because the event loop is what leads to async programming.

Normally, a program consists of events like tasks and messages/requests. There's an event queue which queues these events to be executed. The event loop constantly checks if there are events in the event queue. If so, it executes them.

This is the synchronous part. This way, the event queue gets emptied line by line.

๐Ÿ’ก
Another name for the event queue is the call stack.

๐ŸŒ The Reality

In asynchronous programming, you create a coroutine. A coroutine is basically a function which allows its execution to be paused for a while. During that time, the rest of the event queue can be executed.

Here, the trick is that the execution of the coroutine is leveraged somewhere else. For instance, if you are waiting for an API request to be fulfilled, the API request is being processed on a different computer. So, on our end, it seems the execution has been paused.

With async programming, we can move our focus from the coroutine to the rest of the events in the event queue.

Once the coroutine is completed, the event loop is notified. That way, the event loop can execute the rest of the coroutine starting from its pause point. So, this whole section gets queued at the end of the event queue.

Wait a second! How does the event loop get notified when the coroutine has ended? Ah, good question. The point is that there's a state for every coroutine. It is mostly pending, done or failed. The event loop continuously checks this state. That's how it works magically... Honestly, it's a pretty cool illusion.

But there's a catch. Sometimes you need to wait for a particular coroutine to finish its execution before executing the rest of the program.

For example, you have a website which displays information about movies. Once the user clicks on a movie, you fetch the data from your database (imagine that you've created a coroutine for that). Till the fetching is completed, you go ahead with the rest of your code to display a loading screen. Afterwards, you need the data to display. In such a case, you have to wait. Pretty obvious, ha?

In summary, the event loop is responsible for managing the execution of coroutines and keeping track of their state.

๐Ÿ’ป How This is Used in Code

There are various ways by which asynchronous programming is implemented in practice. In this article, we'll only go through some methods Python and JavaScript use.

๐Ÿค Promises

A promise manages an asynchronous value that may be available in the future. Yet, we aren't sure whether that will be the case. That's because there's a chance for an error to occur. That's the reality :)

๐Ÿ’ก
In fact, a coroutine is built into a promise!

So, you can either resolve a promise (i.e. mark success) or reject it (i.e. mark failure).

In this case, we use setTimeout() to execute the code after 2 seconds. Imagine that we are imitating an API request.

๐Ÿ’ก
In JS setTimeout(), the countdown is being carried out somewhere else because our event loop is busy with executing the rest of our program.
// We only define the promise here...
const prom = new Promise((resolve, reject) => {
    setTimeout(() => {
        const success = true;

        if (success) {
            const data = "Some fetched data";
            resolve(data); // Promise is fulfilled
        } else {
            reject("Error fetching data"); // Promise is rejected
        }
    }, 2000);
});

console.log("Yay, let's execute the promise!")

// Here, the promise gets executed!
prom
    .then((result) => {
        console.log(result); // Code executed when the promise is fulfilled
    })
    .catch((error) => {
        console.error(error); // Code executed if there's an error
    });

// Code to be executed while fetchData is in progress
console.log("Fetching data...");

----- OUTPUT -----
Yay, let's execute the promise!
Fetching data...
Some fetched data

โฉ Async/Await

This is available in both Python and JS. The async keyword defines a coroutine. Then, we mark where the coroutine should pause using the keyword await. Upon the pause break, the program sets on to executing the rest...

Python Implementation

import asyncio
import time

async def say_hi():
    print("Hi!")
    await asyncio.sleep(20) # countdown is being continued somewhere else...

async def count_to_n(n):
    for i in range(1, n+1):
        print(i)
        await asyncio.sleep(1) # again, countdown is being carried out somewhere else

async def main():
    print(f"started at {time.strftime('%X')}")
    hi = asyncio.create_task(say_hi()) # executes the coroutine and pauses upon await
    # as the coroutine is paused, the program executes the rest...
    count = asyncio.create_task(count_to_n(5))
    print("I'm a beast!")
    await count # This pauses main() since it is even a coroutine
    # Basically, we command main() to wait until count has finished executing
    print(f"finished at {time.strftime('%X')}")

asyncio.run(main())
# upon "await count" the program jumps over here...
# since there's nothing left to be executed, main() pauses and waits!

----- OUTPUT -----
started at 19:38:50
Hi!
1
I'm a beast!
2
3
4
5
finished at 19:38:56

JavaScript Implementation

The neat thing about async/await in JS is that, a coroutine defined using async always returns a promise. So,

async function getUserInput() {
    try {
        console.log("Let's start getting the user input!");
        const userInput = setTimeout(() => {
            input = readInput(); // Imagine this as a function that returns a promise!
            console.log("Got the User Input!");
            console.log(`Our user's input is ${userInput}!`);
        }, 2000);
    } catch (err) {
        // This is executed when the promise readInput() returns is rejected!
        console.log(`Oh, crap! We faced an error: ${err}`);
    }
}

userInput = getUserInput();
console.log("See, I can run while getUserInput() waits for the user input!");
console.log("In fact, I can even display a loading screen!");
console.log(
    "This is what userInput really is during the pause break:" + userInput
);

----- OUTPUT -----
Let's start getting the user input!
See, I can run while getUserInput() waits for the user input!
In fact, I can even display a loading screen!
This is what userInput really is during the pause break:[object Promise]
Got the User Input!
Our user's input is Steve Jobs!

โฃ๏ธ Why is Async a Big Deal?

Async allows us to create an illusion of parallel processing without doing so in reality. In computer science, we can achieve parallelism (parallel processing) through multi-threading and multi-processing. However, this is not possible with every programming language.

For instance, JavaScript is a single-threaded language. Thus, parallel processing is impossible with it. Quite amusingly, it's the most popular programming language in the world. Plus, most web pages use it to let the users interact with their services. If it weren't for asynchronous programming, users would have to wait and wait...

Mr Bean Reaction GIF

๐Ÿ‘‹ Conclusion

If you've made it this far, everything that happens under the hood of asynchronous programming should be crystal clear to you! Most importantly, thanks a lot for reading this article! I'm proud that you just read something I wrote.

I'll catch you in another article...

ย