Skip to main content

Command Palette

Search for a command to run...

Async/Await in JavaScript: Writing Cleaner Asynchronous Code

Updated
7 min readView as Markdown

Modern JavaScript applications constantly perform tasks that take time:

  • Fetching data from APIs

  • Reading files

  • Uploading images

  • Connecting to databases

These operations are called asynchronous operations because they do not complete instantly.

Handling asynchronous code used to be difficult and messy. That’s why JavaScript introduced async/await — a cleaner and more readable way to work with asynchronous operations.

In this beginner-friendly guide, you’ll learn:

  • Why async/await was introduced

  • How async functions work

  • Understanding the await keyword

  • Error handling with async code

  • Comparison with promises

By the end, you’ll understand how modern JavaScript handles asynchronous programming elegantly.


Understanding Synchronous vs Asynchronous Code

Before learning async/await, it’s important to understand asynchronous behavior.


Synchronous Code

Synchronous code runs line-by-line.

Example:

console.log("Start");
console.log("Middle");
console.log("End");

Output:

Start
Middle
End

Each line waits for the previous line to finish.


Asynchronous Code

Some operations take time.

Examples:

  • API requests

  • Timers

  • Database queries

  • File reading

JavaScript does not want the entire program to stop while waiting.

So asynchronous code allows other work to continue.


Example with setTimeout

console.log("Start");

setTimeout(() => {
  console.log("Async Task");
}, 2000);

console.log("End");

Output:

Start
End
Async Task

The timer runs in the background.


Why async/await Was Introduced

Before async/await, developers mainly used:

  • Callbacks

  • Promises

Promises improved asynchronous programming, but large promise chains became difficult to read.


Promise Example

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

This works, but deeply chained promises can become confusing.


The Problem

As applications grew larger:

  • Nested .then() blocks reduced readability

  • Error handling became messy

  • Code looked less like normal JavaScript

To solve this, JavaScript introduced async/await.


Async/Await is Syntactic Sugar

async/await is basically a cleaner way to write promises.

It does not replace promises internally.

Instead:

Async/await is syntactic sugar built on top of promises.

This means it makes promise-based code easier to read and write.


Promise vs Async/Await Flow

PROMISE STYLE
--------------
Task
  │
.then()
  │
.then()
  │
.catch()


ASYNC/AWAIT STYLE
-----------------
Task
  │
await result
  │
Normal-looking code
  │
try/catch

What is an Async Function?

An async function is a function declared using the async keyword.


Syntax

async function example() {

}

Important Rule

An async function always returns a promise.


Example

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

greet().then((message) => {
  console.log(message);
});

Output:

Hello

Even though we returned a string, JavaScript automatically wrapped it inside a promise.


Understanding the await Keyword

The await keyword pauses execution until a promise resolves.

It can only be used inside async functions.


Basic Example

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

Now using async/await:

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

  console.log(result);
}

getData();

Output after 2 seconds:

Data received

Understanding the Flow

  1. fetchData() returns a promise

  2. await pauses the function

  3. JavaScript waits for promise completion

  4. Result gets stored in variable

  5. Execution continues


Async Function Execution Flow

Async Function Starts
        │
        ▼
Await Promise
        │
        ▼
Pause Function Execution
        │
        ▼
Promise Resolves
        │
        ▼
Resume Execution
        │
        ▼
Return Result

Why async/await Improves Readability

Compare both approaches.


Promise Version

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

Async/Await Version

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

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

The async/await version looks more like regular synchronous code.

This makes it easier to:

  • Read

  • Debug

  • Maintain


Error Handling with Async Code

Errors in async code are handled using:

try/catch

Example

function fetchData() {
  return new Promise((resolve, reject) => {
    reject("Something went wrong");
  });
}

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

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

getData();

Output:

Something went wrong

Why try/catch is Better Here

Instead of chaining .catch() repeatedly, async/await allows centralized error handling.

This keeps code cleaner.


Multiple Await Example

function stepOne() {
  return Promise.resolve("Step 1 complete");
}

function stepTwo() {
  return Promise.resolve("Step 2 complete");
}

async function runSteps() {
  const result1 = await stepOne();
  console.log(result1);

  const result2 = await stepTwo();
  console.log(result2);
}

runSteps();

Output:

Step 1 complete
Step 2 complete

Real-World Example: API Request

Async/await is commonly used with APIs.

Example:

async function getUsers() {
  try {
    const response = await fetch("https://api.example.com/users");

    const data = await response.json();

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

Common Beginner Mistakes

1. Forgetting async Keyword

Wrong:

function test() {
  await fetchData();
}

This causes error because await works only inside async functions.


Correct Version

async function test() {
  await fetchData();
}

2. Forgetting Error Handling

Always use try/catch with async code.


3. Using await Unnecessarily

Not every function needs await.

Overusing it can slow execution.


Comparison: Promises vs Async/Await

Promises Async/Await
Uses .then() Uses await
Can become nested Cleaner structure
Harder to read in large code More readable
Error handling with .catch() Error handling with try/catch

Real-World Use Cases of Async/Await

Async/await is heavily used in:

  • API requests

  • Authentication systems

  • Database queries

  • File uploads

  • Payment systems

Popular platforms like Netflix and Spotify rely on asynchronous operations extensively in their applications.


Assignment Practice

Try this yourself.


Task

  1. Create a promise that resolves after 2 seconds

  2. Create an async function

  3. Use await to wait for the promise

  4. Print the result

  5. Add error handling using try/catch


Example Solution

function delayedMessage() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("Task completed");
    }, 2000);
  });
}

async function runTask() {
  try {
    const result = await delayedMessage();

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

runTask();

Final Thoughts

Async/await changed the way developers write asynchronous JavaScript.

You learned:

✅ Why async/await was introduced ✅ How async functions work ✅ Understanding the await keyword ✅ Error handling with async code ✅ Comparison with promises

The biggest advantage of async/await is readability.

It allows asynchronous code to look and behave more like normal synchronous code, making applications easier to understand and maintain.

As modern JavaScript development relies heavily on APIs and asynchronous operations, mastering async/await is an essential skill for every developer.