Skip to main content

Command Palette

Search for a command to run...

Async/Await in JavaScript: Writing Cleaner Asynchronous Code

Updated
5 min read

As modern applications grow more dynamic, handling asynchronous operations like API calls, database queries, and timers becomes essential. JavaScript has evolved multiple ways to deal with async behavior—from callbacks to promises, and finally to async/await, which makes asynchronous code look and feel like synchronous code.

If you've ever felt that promises are confusing or hard to read, async/await is here to simplify your life.

In this blog, we’ll cover:

  • Why async/await was introduced

  • How async functions work

  • The concept of await

  • Error handling in async code

  • Comparison with promises

  • Real-world examples for better understanding


🔹 Why Async/Await Was Introduced

Before async/await, developers used:

  1. Callbacks ❌ (led to callback hell)

  2. Promises ✅ (better, but still complex in chaining)

❌ Callback Hell Example

getData(function(a) {
  processData(a, function(b) {
    saveData(b, function(c) {
      console.log(c);
    });
  });
});

This becomes messy and hard to maintain.


✅ Promise Example

getData()
  .then(a => processData(a))
  .then(b => saveData(b))
  .then(c => console.log(c))
  .catch(err => console.log(err));

Better—but still not the easiest to read.


🚀 Enter Async/Await

Async/await was introduced in ES8 (2017) to make asynchronous code:

  • Cleaner

  • More readable

  • Easier to debug

👉 It is basically syntactic sugar over promises.


🔹 What is an Async Function?

An async function always returns a promise.


✅ Syntax

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

✅ Example

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

greet().then(console.log); // Hello World

Even though we return a string, JavaScript wraps it in a promise.


🔹 Understanding the Await Keyword

The await keyword is used inside async functions to pause execution until a promise is resolved.

👉 Think of it as: “Wait here until this task finishes.”


✅ Basic Example

function delay() {
  return new Promise(resolve => {
    setTimeout(() => resolve("Done!"), 2000);
  });
}

async function run() {
  console.log("Start");
  
  let result = await delay();
  
  console.log(result);
  console.log("End");
}

run();

🔍 Output

Start
(2 seconds delay)
Done!
End

🔍 Diagram Idea: Async Function Execution Flow

Start Function
   ↓
Call Async Task
   ↓
Pause (await)
   ↓
Task Completes
   ↓
Resume Execution
   ↓
End Function

🔹 Async/Await Improves Readability

Let’s compare the same example using promises and async/await.


❌ Using Promises

fetch("https://api.example.com/data")
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.log(error));

✅ Using Async/Await

async function fetchData() {
  try {
    let response = await fetch("https://api.example.com/data");
    let data = await response.json();
    console.log(data);
  } catch (error) {
    console.log(error);
  }
}

fetchData();

✔️ Cleaner ✔️ Easier to understand ✔️ Looks like normal synchronous code


🔹 Error Handling with Async/Await

Error handling becomes much easier with try...catch.


✅ Example

async function getData() {
  try {
    let response = await fetch("wrong-url");
    let data = await response.json();
    console.log(data);
  } catch (error) {
    console.log("Error occurred:", error.message);
  }
}

💡 Why This is Better

  • No .catch() chaining

  • Centralized error handling

  • Cleaner debugging


🔹 Throwing Errors in Async Functions

You can also throw custom errors.

async function checkNumber(num) {
  if (num < 0) {
    throw new Error("Negative number not allowed");
  }
  return num;
}

async function run() {
  try {
    let result = await checkNumber(-5);
    console.log(result);
  } catch (error) {
    console.log(error.message);
  }
}

run();

🔹 Real-World Use Cases


✅ 1. Fetching API Data

async function fetchUsers() {
  try {
    let res = await fetch("https://jsonplaceholder.typicode.com/users");
    let users = await res.json();
    console.log(users);
  } catch (err) {
    console.log("Failed to fetch users");
  }
}

✅ 2. Multiple Async Calls

async function getAllData() {
  let user = await fetchUser();
  let posts = await fetchPosts(user.id);
  console.log(posts);
}

⚡ Parallel Execution (Advanced Tip)

async function getData() {
  let [user, posts] = await Promise.all([
    fetchUser(),
    fetchPosts()
  ]);
}

🔹 Promise vs Async/Await

Feature Promises Async/Await
Syntax .then().catch() try...catch
Readability Medium High
Error Handling Chain-based Block-based
Learning Curve Moderate Easier

🔍 Diagram Idea: Promise vs Async/Await Flow

Promises:
Start → then → then → then → catch

Async/Await:
Start → await → await → try/catch → End

🔹 Common Mistakes to Avoid


❌ Using await Outside Async

// ❌ Error
let data = await fetchData();

👉 Must be inside async function.


❌ Forgetting await

async function test() {
  let data = fetchData(); // missing await
  console.log(data); // Promise object
}

❌ Blocking Too Much

Avoid unnecessary sequential awaits:

// Slow
await task1();
await task2();

👉 Use parallel execution if tasks are independent.