1. What is async?

async is a keyword used to define asynchronous functions. When a function is marked async, it implicitly returns a promise.


2. What is await?

await is a keyword used inside an async function to pause execution until a promise is resolved. It can only be used inside an async function and effectively makes asynchronous code behave like synchronous code.


3. How did we handle promises before async/await?

Before async/await, we used Promise chains with .then() and .catch() methods to handle asynchronous code. It required manually chaining operations, which sometimes led to deeply nested or hard-to-read code.

somePromise
  .then((result) => {
    console.log(result);
    return anotherPromise;
  })
  .then((result) => {
    console.log(result);
  })
  .catch((error) => {
    console.error(error);
  });

4. Why did we need async/await?

We needed async/await to solve problems of readability and complexity in promise chaining. It allows asynchronous code to look more like synchronous code, making it easier to follow.


5. How async/await works behind the scenes

  • An async function always returns a promise.
  • The await expression pauses the execution of the async function until the promise is fulfilled (resolved) or rejected.
  • Once resolved, the function resumes and continues with the next line of code.
  • If the promise is rejected, it throws an error that can be caught using try/catch.

Behind the scenes, async/await is syntactic sugar over promises, but they don’t eliminate promises. Instead, they offer a clearer way to write asynchronous code.


6. Example of using async/await

async function fetchData() {
  try {
    const data = await someAPI();
    console.log(data);
  } catch (error) {
    console.error("Error fetching data", error);
  }
}

Here, the fetchData function waits for someAPI() to return its promise, and if the promise is rejected, the error is caught in the catch block.


7. Error Handling in async/await

Error handling in async/await is done using try/catch blocks. This makes it easier to handle errors compared to .catch() used with promises.

async function handleError() {
  try {
    const result = await somePromise;
    console.log(result);
  } catch (error) {
    console.error("Error:", error);
  }
}

If somePromise is rejected, the catch block will handle the error, making the code more manageable.


8. async/await vs. .then()/.catch()

  • Readability: async/await is more readable, resembling synchronous code, while .then() requires chaining and nesting, which can become hard to follow.
  • Error Handling: async/await uses try/catch for error handling, making it more intuitive. In contrast, .then() uses .catch() at the end of the chain for error handling.
  • Complexity: async/await reduces complexity by eliminating deeply nested promise chains.

9. What is a Response Object, fetch(), and Readable Stream?

  • Response Object: Represents the response to a fetch request, containing the HTTP response data. It has methods like .json(), .text(), .blob(), and others to read the response body.

  • fetch(): A modern API to make HTTP requests. It returns a promise that resolves to a Response object.

fetch("https://api.example.com/data")
  .then((response) => response.json()) // Parsing response to JSON
  .then((data) => console.log(data));
  • Readable Stream: Represents a stream of data, especially useful for handling large data like files. It allows reading data in chunks rather than all at once, making it memory efficient.

10. Syntactical Sugar: async/await vs. .then/.catch

async/await is considered syntactical sugar over promises because it simplifies how we write promise-based asynchronous code. Internally, the browser still handles promises, but the async/await syntax makes it more intuitive and cleaner.


11. What is a “thenable”?

A thenable is any object with a .then() method. This means it behaves like a promise and can be used in promise chains. Promises themselves are thenables, but any object implementing the .then() method can also be considered a thenable.

const thenable = {
  then: function (resolve, reject) {
    resolve("This is a thenable");
  },
};

Promise.resolve(thenable).then((result) => console.log(result));
// Output: "This is a thenable"

Example Explanations of Your Code:

1. Await with One Promise:

async function handleProm() {
  const val = await p;
  console.log("hfsf");
  console.log(val);
}
  • The program is paused until p is resolved, then the execution resumes. It prints “hfsf” after p is resolved and logs val.

2. Two Await Statements:

async function handleProm() {
  console.log("awaiting 2 times");
  const val = await p;
  console.log("hfsf");
  console.log(val);

  const val2 = await p;
  console.log("hfsf");
  console.log(val2);
}
  • Here, “awaiting 2 times” is printed immediately, but the execution pauses until p is resolved. It waits for p twice, so val and val2 are printed sequentially after awaiting each promise.

3. Different Promise Times (10s and 5s):

async function handleProm() {
  console.log("awaiting 2 times");
  const val = await p1; // Takes 10 seconds
  console.log("hfsf");
  console.log(val);

  const val2 = await p2; // Takes 5 seconds
  console.log("hfsf2");
  console.log(val2);
}
  • p1 takes 10 seconds to resolve, so “hfsf” and val are logged after 10 seconds. p2 takes 5 seconds, so “hfsf2” and val2 are logged after another 5 seconds.