Demystifying Async/Await: A Comprehensive Guide

Demystifying Async/Await: A Comprehensive Guide

ยท

5 min read



Topics Covered

  • What is async?

  • What is await?

  • How does async/await work behind the scenes?

  • Example of using async/await

  • Error Handling

  • Interviews

  • Async/Await vs Promise.then/.catch


What is Async?

In JavaScript, async is a keyword used before a function to create an asynchronous function.

Q: What is async?

A: Async is a keyword used before a function to create an async function.


Understanding Async Functions

An async function always returns a promise. There are two cases:

Case 1: If you explicitly return a promise.

async function getData() {
  return Promise;
}

Case 2: If you don't return a promise, it wraps the value inside a promise.

async function getData() {
  return "javascript";
}

This function always returns a promise.

Now, calling this function:

async function getData() {
  return "javascript";
}

const dataPromise = getData();
console.log(dataPromise); // Output: Promise {'javascript'}

To retrieve the value from the promise, you use the .then method.

dataPromise.then((res) => console.log(res));

Remember, it always returns a promise in any case; keep this in mind for interviews.


Example of Returning a Promise

Let's create a promise and return it in an async function.

const p = new Promise((resolve, reject) => {
  resolve("Promise Resolved values");
});

async function getData() {
  return p;
}

const dataPromise = getData();
dataPromise.then((res) => console.log(res));

Handling Promises Before Async/Await

Before diving into async/await, let's understand how promises are handled without it.

const p = new Promise((resolve, reject) => {
  resolve("Promise Resolved values");
});

function getData() {
  p.then((res) => console.log(res));
}

getData();

Now, let's handle the same promise using async/await.

const p = new Promise((resolve, reject) => {
  resolve("Promise Resolved values");
});

async function handlePromise() {
  const val = await p;
  console.log(val);
}

handlePromise();

Deep Dive into Async/Await

Consider a promise with a timeout:

const p = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("Promise Resolved values");
  }, 10000);
});

async function handlePromise() {
  const val = await p;
  console.log(val);
}
handlePromise();

In normal handling, JavaScript doesn't wait for the promise to be resolved and moves on to the next line. However, with async/await, the engine waits for the promise to be resolved before moving to the next line.


Understanding Async/Await in Detail

Now, let's explore the difference between normal handling and handling using async/await.

async function handlePromise() {
  console.log("hello world "); // Printed quickly
  const val = await p; // Program waits for 10 seconds
  console.log("javascript ");
  console.log(val);

  // What happens now? Interview question
  // After 10 seconds, both promises will be printed
  const val2 = await p;
  console.log("javascript ");
  console.log(val2);
}
handlePromise();

So, the JavaScript engine is waiting for the promise to be resolved, and once resolved, it moves on to the next line.


Handling Multiple Promises

Now, let's handle two promises:

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("Promise Resolved values");
  }, 10000);
});

const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("Promise Resolved values");
  }, 5000);
});

async function handlePromise() {
  console.log("hello world "); // Printed quickly
  const val = await p1; // Program waits for 10 seconds
  console.log("javascript ");
  console.log(val);

  const val2 = await p2;
  console.log("javascript ");
  console.log(val2);
}
handlePromise();

After 5 seconds, nothing will be printed, but after 10 seconds, both promises will be resolved and printed.


Understanding Async/Await Execution Flow

When this function is executed, it will go line by line as JavaScript is a synchronous, single-threaded language. Let's observe what happens under the call stack. Breakpoints have been set for clarity.

// Call stack flow -> handlePromise() is pushed
// It will log `Hi` to the console
// Next, it encounters await where a promise is supposed to be resolved
// Will it wait for the promise to resolve and block the call stack? No
// Thus, handlePromise() execution gets suspended and moved out of the call stack
// When JavaScript encounters the await keyword, it suspends the function's execution until the promise is resolved
// So, `p` will get resolved after 5 seconds, and handlePromise() will be pushed to the call stack again after 5 seconds
// It will start executing from where it left off
// Now, it will log 'Hello There!' and 'Promise resolved value!!'
// Then, it will check whether `p2` is resolved or not
// It will find that since `p2` will take 10 seconds to resolve, the same process will repeat
// Execution will be suspended until the promise is resolved

// Thus, JS is not waiting, and the call stack is not getting blocked.

// Moreover, in the above scenario, what if p1 takes 10 seconds and p2 5 seconds?
// Even though p2 got resolved earlier, JS is a synchronous, single-threaded language
// It will first wait for p1 to be resolved and then will immediately execute all.

Real-life Example - Fetch API

async function handlePromise() {
  // fetch() => Response Object, which has a body as a Readable stream => Response.json() is also a promise, which when resolved => value
  const data = await fetch("https://api.github.com/users/jatin4224"); // Waited for this line to be resolved
  const res = await data.json(); // Waited for this data JSON to be resolved
  console.log(res);
}
handlePromise();

Error Handling

Error handling in async/await is achieved using try-catch:

async function handlePromise() {
  try {
    const data = await fetch("https://api.github.com/users/jatin4224");
    const res = await data.json();
    console.log(res);
  } catch (err) {
    console.log(err);
  }
}
handlePromise();

Another way to handle errors is using .catch:

handlePromise().catch((err) => console.log(err));

Handling an invalid API URL:

async function handlePromise() {
  try {
    const data = await fetch("https://invalid");
    const res = await data.json();
    console.log(res);
  } catch (err) {
    console.log(err);
  }
}
handlePromise();

handlePromise().catch((err) => console.log(err));

Interview Tips

When asked about async/await, explain the concepts covered in this blog with examples. Be prepared to explain promises if required.


Async/Await vs Promise.then/.catch

Async/await is essentially syntactic sugar around promises. It solves some of the shortcomings of promise chaining and enhances code readability. It is advisable to use async/await as it simplifies code structure and improves clarity.



ย