Async/Await in JavaScript: Writing Cleaner Asynchronous Code

As soon as you start working with real JavaScript applications, you run into asynchronous code.
Fetching data from an API, reading files, waiting for a timer, handling authentication, or talking to a database—all of these things take time. JavaScript cannot just freeze everything and wait. It has to keep moving while those tasks finish in the background.
That is why asynchronous programming exists.
The problem is that older patterns for handling async code were often hard to read. First came callbacks, then promises improved things, and then async/await made asynchronous code feel much more natural.
Why async/await Was Introduced
Before async/await, developers mostly used promises to manage asynchronous operations.
Promises were already a big improvement over deeply nested callbacks, but they could still become a little noisy.
For example:
fetchUser()
.then((user) => {
return fetchPosts(user.id);
})
.then((posts) => {
console.log(posts);
})
.catch((error) => {
console.log(error);
});
This works, but it does not read like normal step-by-step logic. You have to mentally follow the chain of .then() calls.
That is where async/await helps.
It was introduced as a cleaner way to write promise-based code. It does not replace promises under the hood. It just gives you a more readable syntax.
That is why people often say:
async/await is syntactic sugar over promises.
In simple terms, that means it makes promise-based code easier for humans to read and write.
How Async Functions Work
An async function is a function that automatically returns a promise.
Here is a basic example:
async function greet() {
return "Hello";
}
Even though this looks like it returns a normal string, JavaScript actually wraps it in a promise.
So this:
async function greet() {
return "Hello";
}
behaves like this:
function greet() {
return Promise.resolve("Hello");
}
You can use it like this:
greet().then((message) => {
console.log(message);
});
Output:
Hello
The key idea is simple:
asyncmarks a function as asynchronousit always returns a promise
inside that function, you can use
await
The await Keyword Concept
The await keyword can only be used inside an async function.
It tells JavaScript:
“Wait for this promise to finish, then give me the result.”
Here is a simple example:
function getData() {
return new Promise((resolve) => {
setTimeout(() => {
resolve("Data received");
}, 2000);
});
}
async function showData() {
const result = await getData();
console.log(result);
}
showData();
Output after 2 seconds:
Data received
Without await, you would have to use .then(). With await, the code reads more like normal sequential logic.
That is the biggest reason developers like it.
Why It Feels Cleaner
Compare these two versions.
Using promises
function fetchMessage() {
return Promise.resolve("Welcome back");
}
fetchMessage().then((message) => {
console.log(message);
});
Using async/await
function fetchMessage() {
return Promise.resolve("Welcome back");
}
async function displayMessage() {
const message = await fetchMessage();
console.log(message);
}
displayMessage();
Both are correct. But the second version is often easier to read, especially when multiple asynchronous steps are involved.
It feels like:
get the data
store it
use it
That is much closer to how people naturally think through a problem.
A Simple Step-by-Step Async Example
Let’s say you want to simulate loading a user.
function getUser() {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ name: "Alex", age: 24 });
}, 1000);
});
}
async function showUser() {
console.log("Loading user...");
const user = await getUser();
console.log(user);
}
showUser();
Output:
Loading user...
{ name: "Alex", age: 24 }
This reads almost like synchronous code, even though the operation is still asynchronous underneath.
That is the magic of async/await. It improves readability without changing the asynchronous nature of the code.
Comparison with Promises
Promises and async/await are closely related. In fact, async/await is built on top of promises.
Here is the same flow in both styles.
Promise version
function getNumber() {
return Promise.resolve(10);
}
getNumber()
.then((num) => {
console.log(num);
})
.catch((error) => {
console.log(error);
});
Async/await version
function getNumber() {
return Promise.resolve(10);
}
async function printNumber() {
try {
const num = await getNumber();
console.log(num);
} catch (error) {
console.log(error);
}
}
printNumber();
The promise version is perfectly valid, and sometimes it is still useful. But once the logic grows more complex, async/await usually becomes easier to manage.
A good rule of thumb is:
use promises when the chain is small and simple
use
async/awaitwhen you want cleaner step-by-step flow
Error Handling with Async Code
One of the nicest things about async/await is that error handling feels much more natural.
With promises, errors are often handled using .catch().
Example:
fetchData()
.then((data) => {
console.log(data);
})
.catch((error) => {
console.log("Error:", error);
});
With async/await, you can use try...catch.
function fetchData() {
return new Promise((resolve, reject) => {
reject("Something went wrong");
});
}
async function loadData() {
try {
const data = await fetchData();
console.log(data);
} catch (error) {
console.log("Error:", error);
}
}
loadData();
This is a big readability win because error handling stays close to the code that might fail.
Instead of attaching .catch() at the end of a chain, you can wrap the risky part directly in try...catch.
That makes the flow easier to follow.
A Real-World Way to Think About It
Imagine ordering food online.
With promises, the logic feels like this:
place the order
then wait for confirmation
then wait for delivery
catch problems if something fails
With async/await, it feels more like:
place the order
wait for confirmation
wait for delivery
handle any errors if they happen
Same process. Cleaner expression.
That is why async/await became so popular.
What await Does Not Do
One common beginner mistake is thinking await makes code synchronous.
It does not.
JavaScript is still asynchronous. await just pauses that specific async function until the promise settles. It does not block the entire program.
That distinction matters. It only makes the code look more linear and readable.
Final Thoughts
async/await was introduced to make asynchronous JavaScript easier to read, easier to write, and easier to debug.
It is not a replacement for promises at the engine level. It is a cleaner way to work with them. That is why it is called syntactic sugar.
The big ideas to remember are:
asyncmakes a function return a promiseawaitpauses inside that function until a promise resolvestry...catchworks well with async codecompared to
.then()chains,async/awaitusually reads more clearly
Once you get comfortable with it, asynchronous JavaScript starts feeling much less intimidating and much more like normal programming logic.





