How Async/Await Works Internally

·

4 min read

How Async/Await Works Internally

Asynchronous programming is a fundamental concept in javascript, allowing us to perform tasks like making API requests, reading files and handling user interactions without blocking the execution of synchronous code in our javascript.

Async/Await is a powerful feature introduced in ECMAScript 2017 (ES8) that simplifies asynchronous code and makes it more approachable for developers. In this blog post, we will dive into the internals of async/await and explore how it allows us to execute Promises in parallel, significantly improving code readability and maintainability.

Understanding Promises

Before we move forward with async/await, it is essential to revise the concept of Promises. Promises are a core feature of JavaScript that allows us to work with asynchronous operations in a more structured manner.

A Promise can be in one of three states:

  1. Pending: The initial state when the Promise is created, and the asynchronous operation is ongoing.

  2. Resolved (Fulfilled): The state when the asynchronous operation is successful, and a result is available.

  3. Rejected: The state when an error occurs during the asynchronous operation.

Promises provide a clean way to handle asynchronous code, making it easier to work with operations that may take some time to complete.

Introduction to Async/Await

Async/Await is a syntactical feature in JavaScript that is built on Promises to provide a more elegant and readable way to work with asynchronous code. It allows us to write asynchronous code that resembles synchronous code, making our programs easier to understand and maintain.

When we declare a function as async, it returns a Promise implicitly. This means that within an async function, we can use the await keyword to pause the function's execution until a Promise is resolved or rejected.

How Async/Await Works Internally

Now, Let's dive into the internal workings of async/await:

  • Implicit Promise: When we declare a function as async, it effectively returns a Promise, making it awaitable. This Promise is resolved when the function completes successfully or rejected if an error occurs.

  • Await Expression: The await keyword, when used inside an async function pauses the execution of the function until the awaited Promise is settled (either resolved or rejected). This mechanism allows other code to continue running in the meantime, preventing blocking.

  • Promise Resolution and Rejection: When the awaited Promise is resolved, the async function resumes execution from where it left off, with the resolved value available for further processing. If the Promise is rejected, an exception is thrown, which can be caught using a try/catch block within the async function.

  • Event Loop: At the core of JavaScript's asynchronous behavior is the event loop. It continually checks the message queue for pending messages, which can include resolved or rejected Promises. When a Promise settles, a corresponding message is added to the queue, and the event loop processes it, triggering the appropriate then or catch block.

Step-by-Step Execution of Parallel Promises

One of the most powerful aspects of async/await is its ability to execute multiple Promises in parallel while maintaining code readability.

const fetchData =  async() => {
  try {
    const data1 = await fetchDataFromAPI1();
    const data2 = await fetchDataFromAPI2();
    return process(data1, data2);
  } catch (error) {
    console.error(error);
  }
}

In this example, we have two asynchronous operations, fetchDataFromAPI1 and fetchDataFromAPI2. Despite using await, these operations are executed in parallel. Each await keyword pauses the function until its respective Promise is settled, allowing for efficient concurrent execution. This parallelism enhances the performance of our code.

Real-World Use Cases

Async/await is incredibly versatile and applicable in various scenarios, including:

  • Making API requests and handling responses.

  • Reading and writing files asynchronously.

  • Interacting with databases to fetch or update data.

  • Managing complex, asynchronous flows in user interfaces.

Here's an example of using async/await to fetch data from an API:

async function fetchDataFromAPI() {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/data');
    const data = await response.json();
    return data;
  } catch (error) {
    console.error(error);
  }
}

In this code, we await the fetch operation to retrieve data from an API, making the code clean and readable.

Conclusion

Async/await is an essential concept in JavaScript, simplifying asynchronous code and making it more accessible to developers. Understanding how it works internally, along with its ability to execute Promises in parallel, empowers you to write efficient and maintainable asynchronous code. Embrace async/await in your JavaScript projects and enjoy the benefits of improved code quality and developer productivity.