Skip to main content

Command Palette

Search for a command to run...

Async/Await in JavaScript: Writing Cleaner Asynchronous Code

Updated
•5 min read•View as Markdown

Why async/await Was Introduced

JavaScript originally handled asynchronous operations using callbacks. Example:

fetchData(() => {
  getUser(() => {
    getPosts(() => {
      console.log("Done");
    });
  });
});

As applications became larger, this structure became difficult to:

  • read

  • debug

  • maintain

This problem became known as:

Callback Hell

Then promises were introduced. Promises improved async code significantly.

Example:

fetchData()
  .then((data) => {
    return getUser(data);
  })
  .then((user) => {
    return getPosts(user);
  })
  .catch((error) => {
    console.log(error);
  });

This was much cleaner than nested callbacks. But large promise chains still became difficult to manage. That’s why JavaScript introduced:

async/await

to make asynchronous code look more like normal synchronous code.

Real Purpose of async/await

The goal was simple:

Make asynchronous code easier for humans to read.

Instead of deeply chained .then() blocks, developers could now write code that feels natural and sequential.

How Async Functions Work

An async function is created using:

async

keyword.

Example:

async function test() {
  return "Hello";
}

Important Thing About Async Functions

An async function always returns a promise. Even if you return a normal value. Example:

async function test() {
  return "Hello";
}

console.log(test());

Output:

Promise { "Hello" }

JavaScript automatically wraps the return value inside a promise.

Async Function Example

Example:

async function fetchData() {
  return "Data received";
}

fetchData().then((data) => {
  console.log(data);
});

Output:

Data received

Understanding the await Keyword

await is used inside async functions. Its purpose is:

Pause execution until a promise resolves.

Example:

function getData() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("Data received");
    }, 2000);
  });
}

async function fetchData() {
  const result = await getData();

  console.log(result);
}

fetchData();

Output after 2 seconds:

Data received

What await Actually Does

Without await, JavaScript continues immediately. But with:

await promise

JavaScript waits for the promise to complete before moving ahead. This creates code that feels synchronous while still being asynchronous internally.

Thinking Process Behind await

Instead of:

promise.then((data) => {
  console.log(data);
});

you can now write:

const data = await promise;

Much cleaner. Much easier to read.

Sequential Async Flow

Example:

async function process() {
  const user = await getUser();

  const posts = await getPosts(user);

  console.log(posts);
}

This looks almost like normal synchronous code. But internally promises are still being used.

Error Handling with Async Code

One of the biggest advantages of async/await is cleaner error handling. With promises:

fetchData()
  .then((data) => {
    console.log(data);
  })
  .catch((error) => {
    console.log(error);
  });

With async/await:

async function fetchData() {
  try {
    const data = await getData();

    console.log(data);
  } catch (error) {
    console.log(error);
  }
}

Why try...catch Feels Better

try...catch makes async code easier to understand because:

  • success logic stays together

  • error logic stays together

  • nested .catch() chains reduce

This feels much more natural for developers.

What Happens if Promise Rejects

If awaited promise fails:

await somePromise

JavaScript throws an error.

And that error gets caught inside:

catch

block.

Comparison with Promises

Both promises and async/await solve asynchronous programming. But syntax and readability differ.

Promise Style

Example:

getUser()
  .then((user) => {
    return getPosts(user);
  })
  .then((posts) => {
    console.log(posts);
  })
  .catch((error) => {
    console.log(error);
  });

Async/Await Style

Example:

async function fetchPosts() {
  try {
    const user = await getUser();

    const posts = await getPosts(user);

    console.log(posts);
  } catch (error) {
    console.log(error);
  }
}

Why Developers Prefer Async/Await

Because it:

  • looks cleaner

  • feels synchronous

  • improves readability

  • simplifies debugging

  • makes error handling easier

Especially in large applications.

Important Understanding

async/await does NOT replace promises. It is built on top of promises.
Internally:

  • async functions return promises

  • await waits for promises

So promises still exist underneath.

Common Beginner Mistake

Many beginners try using await outside async functions. Example:

const data = await getData();

This causes an error. Because await only works:

  • inside async functions

  • or top-level modules in modern environments

Another Common Confusion

People think await blocks the entire JavaScript engine. It does not. It only pauses execution inside that async function. Other JavaScript code can still continue running.

Real Benefit of Async/Await

The biggest achievement of async/await is not performance. It is:

readability and maintainability.

Developers can now write asynchronous code in a way that feels much closer to normal programming logic.

Final Understanding

async/await was introduced to simplify asynchronous programming in JavaScript.
Async functions automatically return promises, and await pauses execution until promises resolve.
Compared to promise chains, async/await provides:

  • cleaner syntax

  • easier debugging

  • better readability

  • simpler error handling

And even though the syntax looks synchronous, promises are still working internally behind the scenes.

1 views