Async/Await in JavaScript: Writing Cleaner Asynchronous Code
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
awaitError handling in async code
Comparison with promises
Real-world examples for better understanding
🔹 Why Async/Await Was Introduced
Before async/await, developers used:
Callbacks ❌ (led to callback hell)
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()chainingCentralized 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.