Skip to main content

Command Palette

Search for a command to run...

Async/Await in JavaScript: Writing Cleaner Asynchronous Code

Master async/await and simplify your Promises

Updated
4 min read
Async/Await in JavaScript: Writing Cleaner Asynchronous Code
A

Hi, I’m Abdul Samad. A web development learner and tech enthusiast. I write about what I learn, share practical coding tips, and publish in-depth blogs on programming and modern web development.

Check out my full collection of blogs on Hashnode: https://abdulsamad30.hashnode.dev/

Connect with me on X for quick updates and insights: @abdul_sama60108

JavaScript is single-threaded. That means it can only do one thing at a time. But real-world apps need to do things like fetch data from a server, read files, or wait for a user input and none of that happens instantly.

So JavaScript had to figure out how to handle waiting without freezing everything else.

The answer evolved over time: first callbacks, then Promises, and finally async/await. This post focuses on async/await, why it exists, and how to use it properly.


1. Why Async/Await Was Introduced

Promises were a big improvement over callbacks. But once you start chaining multiple .then() calls, the code gets nested and hard to follow. Developers started calling it "Promise chains" and it wasn't always pretty.

Async/await was introduced in ES2017 (ES8) to solve exactly that.

It doesn't replace Promises it's built on top of them. Think of it as syntactic sugar: the same thing underneath, just written in a way that looks and reads like normal synchronous code.

The goal was simple make asynchronous code easier to read, write, and debug.


2. How Async Functions Work

When you put the async keyword in front of a function, two things happen:

  • The function always returns a Promise, even if you return a plain value.

  • It unlocks the ability to use await inside it.

Think of placing an order at a restaurant. The waiter (your async function) takes your order and goes to the kitchen. You don't sit there frozen you're doing other things.

When the food is ready, the waiter comes back with it.

That's what an async function does: it handles the waiting so the rest of your app doesn't have to freeze.

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

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

Output:

Hello from async!

Even though we returned a plain string, the function automatically wrapped it in a resolved Promise.


3. The Await Keyword

await can only be used inside an async function. It tells JavaScript: "pause here and wait for this Promise to resolve, then give me the value."

It doesn't block the entire program just that specific async function.

Everything outside continues running normally.

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

async function getData() {
  console.log("Fetching...");
  const result = await fetchData();
  console.log(result);
}

getData();
console.log("This runs while waiting");

Output:

Fetching...
This runs while waiting
Data received!

Notice how "This runs while waiting" appears before "Data received!" the await paused getData but didn't block the rest of the program.


4. Error Handling with Async Code

With Promises, you handle errors using .catch(). With async/await, you use the standard try/catch block the same one you'd use for synchronous code. This is one of the reasons async/await feels more natural.

function riskyOperation() {
  return new Promise((resolve, reject) => {
    const success = false;
    if (success) {
      resolve("Operation successful!");
    } else {
      reject("Something went wrong.");
    }
  });
}

async function run() {
  try {
    const result = await riskyOperation();
    console.log(result);
  } catch (error) {
    console.log("Caught error:", error);
  }
}

run();

Output:

Caught error: Something went wrong.

If you skip the try/catch and the Promise rejects, you'll get an unhandled Promise rejection. Always wrap await calls in try/catch when there's a chance of failure.


5. Async/Await vs Promises

Both do the same job. The difference is purely in how the code reads.

Here's the same logic written both ways:

With Promises:

function getUser() {
  return Promise.resolve({ name: "Samad" });
}

function getPosts(user) {
  return Promise.resolve(`Posts by ${user.name}`);
}

getUser()
  .then(user => getPosts(user))
  .then(posts => console.log(posts))
  .catch(err => console.log(err));

With Async/Await:

async function loadData() {
  try {
    const user = await getUser();
    const posts = await getPosts(user);
    console.log(posts);
  } catch (err) {
    console.log(err);
  }
}

loadData();

Output (both):

Posts by Samad

The async/await version reads top to bottom like a story. You can follow the logic without mentally tracking .then() chains. That's the real win.

Under the hood, the async/await version compiles down to the same Promise-based code. So you're not giving anything up you're just writing it in a cleaner way.


Wrapping Up

Async/await didn't reinvent how JavaScript handles asynchronous code. It just made writing async code easy. Once you understand that async always returns a Promise and await just unwraps it, everything clicks into place.

If you're already comfortable with Promises, async/await will feel like a natural step forward.

FIN**✌️**