---
title: async/await
source: 'https://academia.sh/en/courses/asynchronous-javascript/async-await'
course: 'Asynchronous JavaScript and the Runtime'
language: en
updated: '2026-08-17T18:09:44+00:00'
license: 'CC BY-SA 4.0'
---

# async/await

An async function always returning a promise, await suspending the function, the syntactic layer's correspondence to a promise chain, and the bug of falling into unintentional sequential waiting.

Promises turned an asynchronous operation into a value, and combinators organized the
waiting. Even so, how the code reads did not change: every step's result is still
processed inside a callback, and the flow does not run top to bottom in the source
text.

This lesson builds the layer that expresses the same promise chains with
sequential-looking syntax. The point to emphasize should be said up front: `async` and
`await` do not introduce a new asynchrony mechanism. Both are a **syntactic layer**
built on top of promises; the event loop's rules apply exactly as they are.

## `async` Always Returns a Promise

A function defined with the `async` keyword returns a promise no matter what it
returns. If it returns an ordinary value, the promise fulfills with that value.

```js
async function fixedMeasurement() {
  return { station: "A1", value: 21.4 };
}

const returned = fixedMeasurement();
console.log("is the returned value a promise:", returned instanceof Promise);
console.log("awaited result:", await returned);
```

```
is the returned value a promise: true
awaited result: { station: 'A1', value: 21.4 }
```

The reverse is true too: an error thrown inside an async function's body does not
propagate outward; it **rejects** the returned promise. The details of error
propagation are the next lesson's subject.

## `await` Suspends the Function, Not the Thread

`await` waits for a promise to settle. While waiting, it suspends **only the function
it is inside**; the thread stays free and control returns to the calling code.

The observable consequence of this is: an async function's body runs **synchronously
up to its first `await`**; everything after that turns into a microtask.

```js
async function processMeasurement() {
  console.log("2 — async body starts synchronously");
  await null;
  console.log("4 — continues in a microtask after the first await");
}

console.log("1 — before the call");
processMeasurement();
console.log("3 — after the call");
```

```
1 — before the call
2 — async body starts synchronously
3 — after the call
4 — continues in a microtask after the first await
```

The awaited value was not even a promise; `await null` still adds a microtask turn.
Formally, the rule is: an `await` expression wraps the awaited value with
`Promise.resolve` and registers what follows as that promise's callback.

Two async functions can advance **side by side**, not nested inside one another. Every
`await` hands control back to the event loop, and the other function's next step
slots in between:

```js
async function firstFlow() {
  console.log("A — step 1");
  await null;
  console.log("A — step 2");
  await null;
  console.log("A — step 3");
}

async function secondFlow() {
  console.log("B — step 1");
  await null;
  console.log("B — step 2");
  await null;
  console.log("B — step 3");
}

firstFlow();
secondFlow();
console.log("synchronous part finished");
```

```
A — step 1
B — step 1
synchronous part finished
A — step 2
B — step 2
A — step 3
B — step 3
```

The two flows interleaved step by step; even so, the two never ran at the same
instant. The single-threaded model was not broken — only the split points were
explicitly marked with `await` expressions.

## The Chain's Counterpart

The same flow can be expressed with either syntax; the promise and the result they
produce are identical.

```js
const SOURCE = {
  A1: { delay: 30, value: 21.4 },
  B2: { delay: 10, value: 19.8 },
  C3: { delay: 20, value: 23.1 },
  D4: { delay: 15, error: "sensor fault" },
};

function requestMeasurement(station) {
  console.log("request started:", station);
  return new Promise((resolve, reject) => {
    const record = SOURCE[station];
    setTimeout(() => {
      if (record.error) reject(new Error(`${station}: ${record.error}`));
      else resolve({ station, value: record.value });
    }, record.delay);
  });
}

async function getAverage() {
  const a = await requestMeasurement("A1");
  const b = await requestMeasurement("B2");
  return (a.value + b.value) / 2;
}

function getAverageChained() {
  return requestMeasurement("A1").then((a) =>
    requestMeasurement("B2").then((b) => (a.value + b.value) / 2),
  );
}

console.log("await syntax:", (await getAverage()).toFixed(2));
console.log("chain syntax:", (await getAverageChained()).toFixed(2));
```

```
request started: A1
request started: B2
await syntax: 20.60
request started: A1
request started: B2
chain syntax: 20.60
```

The difference between the two forms is **readability**. In `await` syntax,
intermediate results are ordinary local variables; reaching the first step's result
in the second step requires no nesting. The difference grows as the number of steps
grows: in chain syntax, every added step demands either a new indentation level or a
hand-carried intermediate object.

## The Sequential-Wait Trap

`await` gives a sequential-looking reading, and that reading can be misleading: waits
written one after another actually **run** one after another.

```js
const SOURCE = {
  A1: { delay: 30, value: 21.4 },
  B2: { delay: 10, value: 19.8 },
  C3: { delay: 20, value: 23.1 },
  D4: { delay: 15, error: "sensor fault" },
};

function requestMeasurement(station) {
  console.log("request started:", station);
  return new Promise((resolve, reject) => {
    const record = SOURCE[station];
    setTimeout(() => {
      if (record.error) reject(new Error(`${station}: ${record.error}`));
      else resolve({ station, value: record.value });
    }, record.delay);
  });
}

for (const station of ["A1", "B2", "C3"]) {
  const measurement = await requestMeasurement(station);
  console.log("arrived:", measurement.station, measurement.value);
}
```

```
request started: A1
arrived: A1 21.4
request started: B2
arrived: B2 19.8
request started: C3
arrived: C3 23.1
```

The output's order gives the diagnosis: every request starts only **after** the
previous one's result arrives. Even though the three stations are independent of each
other, the waits do not overlap; the total time equals the sum of the three delays.

The fix is to separate **starting** the requests from **waiting** for them. Since the
work starts once the promises are created, all of them are created first, then
awaited at a single point.

```js
const SOURCE = {
  A1: { delay: 30, value: 21.4 },
  B2: { delay: 10, value: 19.8 },
  C3: { delay: 20, value: 23.1 },
  D4: { delay: 15, error: "sensor fault" },
};

function requestMeasurement(station) {
  console.log("request started:", station);
  return new Promise((resolve, reject) => {
    const record = SOURCE[station];
    setTimeout(() => {
      if (record.error) reject(new Error(`${station}: ${record.error}`));
      else resolve({ station, value: record.value });
    }, record.delay);
  });
}

const promises = ["A1", "B2", "C3"].map((station) =>
  requestMeasurement(station).then((measurement) => {
    console.log("arrived:", measurement.station, measurement.value);
    return measurement;
  }),
);
const measurements = await Promise.all(promises);
console.log("total:", measurements.reduce((t, m) => t + m.value, 0).toFixed(1));
```

```
request started: A1
request started: B2
request started: C3
arrived: B2 19.8
arrived: C3 23.1
arrived: A1 21.4
total: 64.3
```

All three requests started immediately; the results arrived in order of delay. Total
wait equals not the sum of the three delays but the **largest** one.

The criterion is a simple question: **does this step genuinely depend on the
previous one's result?** If the answer is no, an `await` inside a loop is a bug. If
the answer is yes — for example, if the second station's identity comes out of the
first measurement — sequential waiting is correct and necessary.

## Top-Level `await`

At the module level, `await` can be used without being inside a function. The last
lines of this lesson's examples use exactly this. The details tied to module forms
are the Modules, Tooling and the Ecosystem course's subject; the only thing that
needs to be known here is that a top-level await **delays the module's evaluation**.

## Summary

- An async function always returns a promise; an error thrown in its body rejects the
  returned promise.
- `await` suspends only its own function; the thread stays free, and control returns
  to the calling code.
- The body runs synchronously up to the first `await`; what follows turns into a
  microtask — even awaiting a non-promise value adds a turn.
- `await` syntax and chain syntax produce the same promise; the difference is
  readability.
- Using `await` inside a loop for independent jobs serializes the waits; the correct
  pattern is to start the jobs and await them at a single point.

## Next Step

This lesson deliberately left the error path aside. The next lesson takes it up:
where an error thrown in an async body goes, when `try`/`catch` and `catch` syntax
diverge, in which case the combinators lose error information, and how a rejection no
one handles gets reported to the runtime. The measurement stream will also become
resilient to the faulty station there.
