Async / Await in JavaScript

Async/await is a way to write code that handles things that take time — like fetching data from a server, reading a file, or waiting for a timer — without your whole program freezing up.

Think of it like ordering food at a restaurant. You place your order and wait. But while the kitchen is cooking, you don't just stand there frozen — you talk to your friends, check your phone, and carry on. When the food is ready, the waiter brings it to you. That is exactly how async/await works.

Before async/await existed, developers used Promises with .then() and .catch(). Async/await is built on top of Promises — it is just a cleaner and easier way to write the same code.

javascript

// Without async/await (using .then)
fetch("https://jsonplaceholder.typicode.com/todos/1")
  .then(response => response.json())
  .then(data => console.log(data.title))
  .catch(err => console.error(err));

// With async/await (same thing, easier to read)
async function getPost() {
  try {
    const response = await fetch("https://jsonplaceholder.typicode.com/todos/1");
    const data = await response.json();
    console.log(data.title);
  } catch (err) {
    console.error(err);
  }
}
getPost();

Both examples do the same thing. But the async/await version reads top to bottom, just like normal code. No nested callbacks. No chain of .then(). Much easier to follow.

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

  • The function always returns a Promise — even if you return a plain value like a string or number
  • You are now allowed to use the await keyword inside it

javascript

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

// Even though we returned a plain string,
// the function returns a Promise
greet().then(message => console.log(message)); // Hello!

You can use async with any type of function:

javascript

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

// Arrow function
const greet = async () => {
  return "Hello!";
};

// Object method
const user = {
  getName: async function() {
    return "Alice";
  }
};

// Class method
class UserService {
  async getUser() {
    return { name: "Alice" };
  }
}
Remember: You do not always need to call .then() on an async function. If you are calling it from inside another async function, just use await in front of it.

await tells JavaScript: "Stop here and wait for this Promise to finish before moving to the next line." But here is the important part — it only pauses inside the async function. The rest of your app keeps running normally.

Think of it like pressing pause on a video. The video pauses, but everyone else in the room can still move around. Other parts of your code are not blocked.

javascript

function wait(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function run() {
  console.log("Start");
  await wait(2000); // pauses here for 2 seconds
  console.log("Done after 2 seconds");
}

run();
console.log("This runs immediately — not blocked");

You can only use await inside an async function. Trying to use it outside will cause a syntax error.

javascript

async function loadData() {
  const response = await fetch("https://jsonplaceholder.typicode.com/todos/1");
  const data = await response.json();
  console.log(data.title); // delectus aut autem
  console.log(data.completed); // false
}

loadData();
Watch out: When you await things one by one inside a loop, each one waits for the previous to finish. If you have independent tasks, use Promise.all() to run them at the same time and save time.

When a Promise rejects (something goes wrong), await throws an error. To handle it, wrap your await calls inside a try/catch block.

Think of try/catch like a safety net. You try to do something that might fail — if it does, the catch block runs, and your app does not crash.

javascript

async function getUser(id) {
  try {
    const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
    if (!response.ok) {
      throw new Error("User not found");
    }
    const user = await response.json();
    console.log(user.name);
  } catch (error) {
    console.error("Something went wrong:", error.message);
  }
}

getUser(1);   // Leanne Graham
getUser(999); // Something went wrong: User not found

You can also add a finally block for code that should always run, no matter what happens — like hiding a loading spinner:

javascript

async function loadData() {
  try {
    const res = await fetch("https://jsonplaceholder.typicode.com/posts/1");
    const data = await res.json();
    console.log(data.title);
  } catch (err) {
    console.error("Failed to load:", err.message);
  } finally {
    console.log("Loading finished"); // always runs
  }
}

loadData();
Note: fetch() only rejects when there is a network failure. A 404 or 500 response does not throw an error by default. Always check response.ok or response.status manually.

Async/await does not replace Promises — it is built on top of them. Under the hood, an async function returns a Promise. await just pauses execution until that Promise resolves. You are writing the same logic, just in a cleaner way.

javascript

// Using Promises (.then chains)
function getPost() {
  return fetch("https://jsonplaceholder.typicode.com/posts/1")
    .then(res => res.json())
    .then(data => {
      console.log(data.title);
    })
    .catch(err => console.error(err));
}

// Same thing using async/await
async function getPost() {
  try {
    const res = await fetch("https://jsonplaceholder.typicode.com/posts/1");
    const data = await res.json();
    console.log(data.title);
  } catch (err) {
    console.error(err);
  }
}

Both versions produce the same result. Use async/await when your code has multiple steps that depend on each other. Use Promise.all() when you want multiple things to happen at the same time:

javascript

// Running multiple tasks in parallel
async function loadDashboard() {
  const [users, posts] = await Promise.all([
    fetch("https://jsonplaceholder.typicode.com/users").then(r => r.json()),
    fetch("https://jsonplaceholder.typicode.com/posts").then(r => r.json())
  ]);
  console.log("Users:", users.length);  // 10
  console.log("Posts:", posts.length);  // 100
}

loadDashboard();
Tip: When you have tasks that do not depend on each other, use Promise.all() instead of multiple await calls. It runs them in parallel and finishes much faster.
  • async makes a function always return a Promise
  • await pauses execution inside an async function until a Promise resolves
  • The rest of your code outside the async function keeps running — it is not blocked
  • Use try/catch to handle errors inside async functions
  • Add a finally block for cleanup code that always needs to run
  • Async/await is built on top of Promises — they are not competing concepts
  • Use Promise.all() to run multiple independent tasks at the same time
  • await can only be used inside an async function

What's next? Now that you know how async and await work, let's use them with real HTTP requests in the next tutorial.

  • What does the async keyword do to a function?
  • What does await do, and where can you use it?
  • What is the difference between async/await and Promises?
  • What happens if you use await outside an async function?
  • How do you handle errors in async/await?
  • What is the difference between using try/catch vs .catch() with async functions?
  • How would you run two API calls at the same time using async/await?
  • Does await block the main thread?
  • Can you use async/await with forEach? What is the problem if you do?
  • What does an async function return if you return a plain value like a string?
Promises
Fetch API