JavaScript runs in a single thread, which means long-running operations — like network requests or file reads — would freeze the browser if they ran synchronously. Async/await is the modern solution.

The problem: blocking code

// This would freeze the browser if fetch took a second:
const data = fetch('https://api.example.com/data'); // WRONG

Promises

A Promise is an object that represents a value that may not be available yet. It's either pending, fulfilled, or rejected.

const promise = fetch('https://api.example.com/data');
promise
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error(error));

This works but chained .then() calls get messy with complex logic.

Async/Await

async and await make asynchronous code look synchronous, which is much easier to read:

async function getData() {
  const response = await fetch('https://api.example.com/data');
  const data = await response.json();
  console.log(data);
}
getData();

The await keyword pauses execution inside the async function until the Promise resolves. The rest of the program continues running — this is not blocking.

Error handling

Wrap await calls in try/catch:

async function getData() {
  try {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Request failed:', error);
  }
}

Running multiple async operations

To run multiple operations in parallel:

const [users, posts] = await Promise.all([
  fetch('/api/users').then(r => r.json()),
  fetch('/api/posts').then(r => r.json()),
]);

Promise.all waits for both to complete. If either fails, the whole thing rejects.

When to use async/await

Any time you're working with:

  • fetch() — network requests
  • setTimeout() wrapped in a promise
  • File system operations in Node.js
  • Database queries in server code

async/await doesn't change how JavaScript works — it's syntactic sugar over Promises. Understanding Promises is still useful for reading older code and handling complex scenarios.