Async/Await Semantics

An `async` function always returns a Promise, while `await` pauses that function's continuation until the awaited value settles. This suspension does not block the JavaScript thread, so other work can continue while the asynchronous operation is pending.

Calling an `async` function immediately produces a Promise. A normal return value becomes a fulfilled Promise, while throwing an exception causes the returned Promise to reject.

`await` can be used inside an `async` function. It evaluates its operand and waits for the resulting value to settle; non-Promise values are treated as already fulfilled. The function's execution resumes later with the fulfillment value, or throws at the `await` expression when the Promise rejects.

The important distinction is between suspending a function and blocking the JavaScript thread. While an `async` function is suspended at `await`, the event loop can process other callbacks, timers, rendering work, and Promise continuations.

Code after `await` therefore runs asynchronously, even when the awaited Promise is already fulfilled. This makes `await` convenient for expressing sequential asynchronous steps while retaining Promise-based semantics.

Multiple independent asynchronous operations should usually be started before awaiting them, often with `Promise.all`, rather than awaiting each operation serially when there is no dependency between them.

Key points

Interview questions

What does an `async` function return?

An `async` function always returns a Promise. If it returns a normal value, that value becomes the fulfillment value of the Promise. If it throws, the returned Promise is rejected. This means callers generally consume the result with `await` or Promise methods such as `.then()`.

Does `await` block the JavaScript thread?

No. `await` suspends execution of the current async function at that point, but it does not block the JavaScript thread. While the awaited operation is pending, the runtime can process other eligible work. When the Promise settles, the async function's continuation is scheduled to resume.

What happens if the Promise awaited by `await` rejects?

The `await` expression behaves as though the rejection were thrown at that point in the async function. A surrounding `try/catch` can handle the error. If the error is not handled inside the function, the async function's returned Promise becomes rejected.

Why might sequential `await` calls be slower than using `Promise.all`?

If two operations are independent, awaiting the first before starting the second unnecessarily introduces a dependency. The second operation cannot begin until the first finishes in that structure. Starting both operations first and awaiting `Promise.all` allows their asynchronous work to overlap. This can reduce total waiting time when the operations do not depend on one another.

What happens when you `await` a non-Promise value?

The value is treated as an already-fulfilled result for the purposes of `await`. The async function still resumes through its asynchronous continuation semantics rather than turning the surrounding function into a synchronous one. For example, `await 5` ultimately produces `5`. This behavior makes code generic over values and thenables without requiring an explicit Promise check.