JavaScript Async Await

Async and await are keywords that let you write Promise-based code that reads like ordinary, top-to-bottom synchronous code.

The async and await keywords

Async/await is syntax built on top of Promises. Marking a function with async makes it automatically return a Promise, and inside such a function you can use await to pause until a Promise settles. The result is asynchronous code that flows from top to bottom, without chains of .then() calls, while still being non-blocking under the hood.

  • An async function always returns a Promise, even if you return a plain value.
  • await can only be used inside an async function (or at the top level of a module).
  • await pauses that function until the awaited Promise resolves, then gives you its value.
  • Errors are handled with a normal try/catch block instead of .catch().

The same task with then vs await

<!DOCTYPE html>
<html>
<body>

<h2>then vs await</h2>

<script>
// Promise chain
function loadUser() {
  return fetch("/user/1").then((r) => r.json());
}

// async / await equivalent
async function loadUserAsync() {
  const response = await fetch("/user/1");
  const user = await response.json();
  return user;
}
</script>

</body>
</html>

Handling errors with try/catch

Because await unwraps a Promise, a rejected Promise behaves like a thrown error. This means you can wrap awaited calls in a try/catch block, the same way you handle synchronous errors, which keeps success and failure logic close together and easy to read.

Awaiting with error handling

<!DOCTYPE html>
<html>
<body>

<h2>Async Error Handling</h2>

<script>
async function getData() {
  try {
    const response = await fetch("/data.json");
    if (!response.ok) {
      throw new Error("Request failed: " + response.status);
    }
    const data = await response.json();
    return data;
  } catch (error) {
    console.log("Could not load data:", error.message);
    return null;
  } finally {
    console.log("Attempt finished");
  }
}
</script>

</body>
</html>
Note: Awaiting tasks one after another when they do not depend on each other makes your code slower than it needs to be. Each await waits for the previous one to finish before starting the next.

Running work in parallel

When independent tasks can run at the same time, start them first and await them together with Promise.all(). This runs them concurrently instead of one at a time, which can dramatically reduce total waiting time.

Sequential vs parallel awaits

<!DOCTYPE html>
<html>
<body>

<h2>Sequential vs Parallel Awaits</h2>

<script>
// Slow: waits for each in turn
async function slow() {
  const a = await getUser();
  const b = await getPosts();
  return [a, b];
}

// Fast: both start immediately, then await together
async function fast() {
  const [a, b] = await Promise.all([getUser(), getPosts()]);
  return [a, b];
}
</script>

</body>
</html>
ApproachReadabilityError handling
CallbacksNests deeplyManual, per callback
Promises (.then)Flat chain.catch() at the end
async / awaitReads like sync codetry / catch block
Note: Async/await and Promises are not competing choices. Async functions are simply a nicer way to work with the very same Promises, so you can freely mix them, for example awaiting Promise.all().

Exercise: JavaScript Async

What actually happens when the `await` keyword is used inside an async function?