ToolPopToolPop
JavaScript · Lesson 5 of 18

Async / await without the headache

12 min readUpdated 24 Jun 2026

JavaScript is single-threaded. One thing at a time. So how does your browser load five images, run a script, and play a video, all at once? Because most of those things are waiting on something else (network, disk, timers), and JavaScript hands the waiting off and moves on.

The system that handles this is called the event loop. Async/await is the syntax that makes it readable.

Diagram
rendering diagram...
Event loop in one diagram: hot path stays fast, slow stuff waits in a queue

The Swiggy metaphor

You place an order on Swiggy. The app does not freeze for 30 minutes waiting for biryani. It says "okay, order placed," shows you a tracker, and lets you scroll Instagram. When the food arrives, you get a notification.

That is async. The order is a promise. The notification is the resolve. If the restaurant cancels, that is a reject.

Callbacks were the original mess

Before promises, async work was passed a callback function to run when done.

js
placeOrder(items, function (err, order) {
  if (err) return console.error(err);
  trackOrder(order.id, function (err, status) {
    if (err) return console.error(err);
    notifyUser(status, function (err) { /* and so on */ });
  });
});

This nesting got a nickname: callback hell. Promises fixed it.

Promises, in one paragraph

A promise is an object that represents a future value. It has three states: pending, fulfilled, rejected. You chain work with .then() and handle errors with .catch().

js
placeOrder(items)
  .then(order => trackOrder(order.id))
  .then(status => notifyUser(status))
  .catch(err => console.error(err));

Flatter, readable, but still a bit ceremonial.

async/await is just nicer promises

async marks a function as asynchronous. await pauses inside that function until a promise settles. The code reads top-to-bottom like normal.

js
async function order() {
  try {
    const order  = await placeOrder(items);
    const status = await trackOrder(order.id);
    await notifyUser(status);
  } catch (err) {
    console.error(err);
  }
}

try/catch works exactly like synchronous error handling. This is the single best feature added to JS in the last decade.

await only works inside async functions, or at the top level of an ES module. Random scripts cannot just await.

Promise.all for parallel work

Sequential await is fine when each step needs the previous one. When they do not, you waste time waiting in line.

js
const [user, cart, offers] = await Promise.all([
  fetchUser(),
  fetchCart(),
  fetchOffers(),
]);

All three fire at once, you wait for the slowest. If any one rejects, the whole Promise.all rejects. Use Promise.allSettled if you want every result regardless.

The event loop, in 60 seconds

JavaScript has a call stack (current work) and a queue (waiting work). When an async task finishes, its callback joins the queue. The event loop picks from the queue only when the stack is empty. That is why a slow synchronous function blocks everything, including UI updates.

In practice: never do heavy CPU work on the main thread. Offload to a worker, or break it up.

Quick recap

  • Async exists so the browser does not freeze while waiting on network or timers.
  • Promises replaced callbacks. Async/await replaced promise chains.
  • Always wrap awaited code in try/catch.
  • Parallel work? Promise.all. Independent results? Promise.allSettled.

Next: fetch, the modern way to actually call an API.

Free tools you can use while you learn

Common questions

Q.What is the difference between async/await and promises?
A.async/await is syntax sugar on top of promises. Under the hood it is the same thing. await pauses the async function until the promise resolves, then continues. Promises are still the actual mechanism.
Q.Can I use await at the top level of a file?
A.Yes, in ES modules (any file with import/export). This is called top-level await. In a function, await still requires the function to be marked async.
Chai0/1 done

Watching quietly. Tap me if you want a tip.

JS Playground
console
// hit run to see output

Try this (0 of 1 done)

  1. 1

    Write an async function that logs "step 1", waits 200ms, then logs "step 2". Call it.

    show answer
    async function go() {
      console.log('step 1');
      await wait(200);
      console.log('step 2');
    }
    go();