
In the previous blog, you learned how promises help manage asynchronous operations in a cleaner way compared to callbacks.
Promises improved readability, but they still have a limitation.
When multiple asynchronous steps are chained together, the code can still become difficult to follow.
fetchData()
.then(data => processData(data))
.then(result => saveData(result))
.catch(error => console.log(error));
This is better than callbacks, but it still feels like a chain rather than a natural flow.
This is exactly why async and await were introduced.
Why Async/Await Was Introduced
Async/await was introduced to make asynchronous code look and behave more like synchronous code.
The goal is simple.
Write asynchronous logic in a way that is easy to read, easy to understand, and easy to maintain.
Instead of chaining .then(), you write code step by step.
What is an Async Function?
An async function is a function that always returns a promise.
async function greet() {
return "Hello";
}
Even though it returns a simple value, JavaScript wraps it in a promise.
So internally, it behaves like:
Promise.resolve("Hello");
The Await Keyword
The await keyword is used inside async functions.
It pauses the execution of the function until the promise is resolved.
function fetchData() {
return new Promise(resolve => {
setTimeout(() => resolve("Data received"), 2000);
});
}
async function getData() {
const data = await fetchData();
console.log(data);
}
getData();
Here, await makes the code look synchronous.
Instead of using .then(), you directly assign the result to a variable.
How This Improves Readability
Compare both approaches.
Using Promises
fetchData()
.then(data => processData(data))
.then(result => console.log(result));
Using Async/Await
async function run() {
const data = await fetchData();
const result = await processData(data);
console.log(result);
}
The second version reads like a normal flow.
This makes complex logic much easier to understand.
Error Handling with Async/Await
Handling errors with promises requires .catch().
With async/await, you can use try...catch.
async function getData() {
try {
const data = await fetchData();
console.log(data);
} catch (error) {
console.log(error);
}
}
This looks cleaner and more structured.
Async/Await is Syntactic Sugar
Async/await is not a replacement for promises.
It is built on top of promises.
You can think of it as a simpler way to write promise-based code.
Under the hood, everything still uses promises.
Execution Flow
Even though await looks like it pauses execution, JavaScript is still non-blocking.
Only the async function waits.
The rest of the program continues to run.
Real-World Example
function fetchUser() {
return new Promise(resolve => {
setTimeout(() => resolve("User data"), 2000);
});
}
function fetchPosts() {
return new Promise(resolve => {
setTimeout(() => resolve("User posts"), 2000);
});
}
async function getUserDetails() {
const user = await fetchUser();
const posts = await fetchPosts();
console.log(user);
console.log(posts);
}
getUserDetails();
This is how real applications fetch and process data step by step.
Promise vs Async/Await
Promises:
.then().then().then()
Async/Await:
step → step → step
Async/await removes chaining and makes code more readable.
Common Mistakes
One common mistake is using await outside an async function.
Another is forgetting that async functions always return promises.
Also, writing sequential await calls when they could run in parallel can slow down performance.
Final Thought
Async/await makes asynchronous code feel natural.
Instead of thinking in terms of callbacks or chains, you start writing code in a simple, step-by-step flow.
It improves readability without changing how JavaScript works underneath.
Once you get comfortable with async/await, writing asynchronous code becomes much easier and more intuitive.






