Advanced25 min read

Async / Await

Write cleaner asynchronous code using async functions and the await keyword as syntactic sugar over Promises.

async Functions

The async keyword placed before a function declaration creates an async function. Async functions always return a Promise — even if you return a plain value, it is automatically wrapped in Promise.resolve().

Function declaration:

javascript
async function fetchData() {
  return 'Hello';
}
fetchData().then(value => console.log(value)); // 'Hello'

Function expression:

javascript
const fetchData = async function() {
  return 'Hello';
};

Arrow function:

javascript
const fetchData = async () => {
  return 'Hello';
};

Object method:

javascript
const api = {
  async getData() {
    return 'data';
  }
};

Since async functions return Promises, you can use .then() on the result. But the real power comes from using await inside them.

The await Keyword

The await keyword can only be used inside an async function (or at the top level of an ES module). It pauses the execution of the async function until the Promise it is waiting on settles.

javascript
async function loadUser() {
  const response = await fetch('/api/user');
  const user = await response.json();
  console.log(user.name);
}

Without await, the same code using .then() would look like this:

javascript
function loadUser() {
  return fetch('/api/user')
    .then(response => response.json())
    .then(user => console.log(user.name));
}

Key points:

  • await pauses only the async function — the rest of your code continues running.
  • The awaited value is the resolved value of the Promise.
  • If the Promise rejects, await throws the rejection reason as an error.
  • You can await any value, not just Promises — non-Promise values are returned immediately.
javascript
async function demo() {
  const a = await 42;      // Immediately resolves to 42
  const b = await Promise.resolve('hello'); // Resolves to 'hello'
  console.log(a, b); // 42 'hello'
}

Error Handling with try/catch

With Promises, you handle errors using .catch(). With async/await, you use the familiar try/catch statement instead.

javascript
async function loadData() {
  try {
    const response = await fetch('/api/data');
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error('Failed to load:', error.message);
  }
}

You can wrap multiple await calls in a single try block. If any of them rejects, execution jumps to the catch block:

javascript
async function processOrder() {
  try {
    const user = await getUser();
    const order = await createOrder(user.id);
    const receipt = await sendReceipt(order.id);
    return receipt;
  } catch (error) {
    // Handles errors from ANY of the three await calls
    console.error('Order failed:', error);
  }
}

You can also add a finally block for cleanup that runs regardless of success or failure:

javascript
async function fetchWithLoading() {
  showSpinner();
  try {
    const data = await fetchData();
    displayData(data);
  } catch (error) {
    showError(error);
  } finally {
    hideSpinner(); // Always runs
  }
}

The combination of async/await + try/catch is the modern standard for handling asynchronous errors in JavaScript.

Common Patterns

Sequential vs Parallel execution:

When you await multiple Promises one after another, they run sequentially (each waits for the previous to finish):

javascript
// Sequential — takes ~2 seconds total
const user = await fetchUser();    // 1 second
const orders = await fetchOrders(); // 1 second

If the operations are independent, use Promise.all() to run them in parallel:

javascript
// Parallel — takes ~1 second total
const [user, orders] = await Promise.all([
  fetchUser(),
  fetchOrders()
]);

Avoid the "waterfall" anti-pattern when operations do not depend on each other.

Async IIFE — immediately invoked async function expression for quick execution:

javascript
(async () => {
  const data = await fetchData();
  console.log(data);
})();

Top-level await — in ES modules, you can use await at the top level without wrapping in an async function:

javascript
// In an ES module (.mjs or type="module")
const data = await fetchData();
console.log(data);

Top-level await is useful in module initialization code but should be used carefully since it blocks the module from finishing loading until the Promise resolves.

async/await Example

html
<div id="output"></div>

<script>
  // Simulate an API call
  function simulateAPI(data, delay) {
    return new Promise(resolve => {
      setTimeout(() => resolve(data), delay);
    });
  }

  const output = document.getElementById('output');
  output.innerHTML = '<p>Loading...</p>';

  async function loadDashboard() {
    try {
      // Run in parallel with Promise.all
      const [user, settings] = await Promise.all([
        simulateAPI({ name: 'Alice' }, 300),
        simulateAPI({ theme: 'dark' }, 200)
      ]);

      output.innerHTML = `
        <p>User: ${user.name}</p>
        <p>Theme: ${settings.theme}</p>
        <p>Dashboard loaded!</p>
      `;
    } catch (error) {
      output.innerHTML = `<p>Error: ${error.message}</p>`;
    }
  }

  loadDashboard();
</script>

What does the await keyword do?

Ready to practice?

Create your free account to access the interactive code editor, run challenges, and track your progress.