Async / Await in Javascript

Async / Await in Javascript

JavaScript is single-threaded, which means it can do only one thing at a time.
So to handle tasks that take time (like fetching data from a server), JavaScript uses asynchronous programming, which allows the program to continue running while waiting for these tasks to complete.

Traditionally, asynchronous code was handled using callbacks or promises. However, both can make the code difficult to read and maintain, especially when you have a sequence of asynchronous operations (this is often referred to as "callback hell" or "promise chaining").

async function fetchData() {
  return "Data fetched!";
}

// This is equivalent to:
function fetchData() {
  return Promise.resolve("Data fetched!");
}

Let's say you are writing a controller function whose task is to perform some task on a data set that is being received from an API. Now, if suppose you make this function as a normal function, then the API fetching part may take some time and so the rest of your code that has nothing to do with that 'API data' you are going to get is waiting for you. { It's like you are standing in a movie line even though you don't want to watch a movie }

function fetchDataSync() {
  // Simulating an API fetch
  const data = fetch('https://api.example.com/data');

  // This line waits until the API fetch is complete
  console.log('Fetched data:', data);

  // Other unrelated code will also wait for the data fetch
  console.log('Doing something else that doesn’t need the data');
}

So, we have a solution: why not group the lines of code that deal with the fetched data inside a label named async? This way, JavaScript knows to ignore those lines and continue executing the lines that aren't labeled. But wait a minute, does that mean the entire async block of code will execute at the very end of our program!?

The answer is a big NO and that's where "await" comes into the picture.

'await' keyword tells javascript, which line inside the async block has to be waited for to be executed to move any further with the execution inside that async block.

function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function asyncFunction() {
  console.log('Start');
  await delay(2000); // Waits 2 seconds
  console.log('After 2 seconds');
}

asyncFunction();
console.log('End');

OUTPUT :

Start
End
After 2 seconds

As you can see above, the line before the await keyword doesn't have to wait for its execution, but the lines below it will wait. So, when you create a controller, there will be lines above the API call that are not related to the API call itself. These lines will execute immediately, just like synchronous code. Then, the line with the API call will execute with the await keyword, ensuring that the lines below, which depend on the fetched data, will wait for the await statement to return. This avoids any unexpected behavior in our code.

async function myController(req, res) {
  // Task 1: Log the incoming request - not dependent on the API call
  console.log('Received request:', req.body);

  // Task 2: Fetch data from an API
  const apiData = await fetch('https://api.example.com/data');
  const data = await apiData.json(); // Waiting for the data to be fetched

  // Task 3: Now process the data after it's fetched
  console.log('Fetched API data:', data);

  // Task 4: Send a response back to the client
  res.json({ message: 'Success', data });
}

How async/await makes code more readable and efficient than promises ?

Without async/await (using Promises):

function fetchData() {
  fetch('https://jsonplaceholder.typicode.com/users/1')
    .then(response => response.json())
    .then(data => {
      console.log('User data:', data);
      return fetch('https://jsonplaceholder.typicode.com/posts?userId=1');
    })
    .then(response => response.json())
    .then(posts => {
      console.log('User posts:', posts);
    })
    .catch(error => {
      console.log('Error:', error);
    });
}

fetchData();

With async/await:

async function fetchData() {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/users/1');
    const user = await response.json();
    console.log('User data:', user);

    const postsResponse = await fetch(`https://jsonplaceholder.typicode.com/posts?userId=${user.id}`);
    const posts = await postsResponse.json();
    console.log('User posts:', posts);
  } catch (error) {
    console.log('Error:', error);
  }
}

fetchData();

What Happens:

  • Without async/await: The code uses .then() for each asynchronous operation, resulting in nested blocks that can be hard to follow, especially with multiple asynchronous steps.

  • With async/await: The code reads top-to-bottom, in a declarative way making it much clearer what is happening and in what order.