JavaScript Async Functions and Async Iteration
Understand async functions, await, sequencing versus concurrency, asynchronous errors, async iterables, for await...of, async generators, and Array.fromAsync.
Intermediate
Async functions return Promises
An async function always returns a Promise. Its body can use await, but calling the function does not give you the eventual value directly.
Output
Ready to run.
Quick check
What does calling an async function return?
async function answer() { return 42; }
const result = answer();
Returning and throwing inside async functions
A normal return from an async function fulfills its returned Promise. A throw makes that Promise reject. This gives async functions the same Promise-based outcome model used by the previous lesson.
Output
Ready to run.
Quick check
What happens to the Promise returned by an async function when its body throws?
async function fail() { throw new Error("x"); }
await unwraps a Promise outcome
Inside an async function, await evaluates an expression and waits for its Promise outcome. If it fulfills, await produces the fulfillment value. If it rejects, await throws that rejection reason at the await point.
Output
Ready to run.
Quick check
If the awaited Promise rejects, what does await do?
async function run() {
await Promise.reject(new Error("no"));
}
await does not block the JavaScript thread
await suspends the current async function until the awaited operation settles. It does not freeze the JavaScript thread or stop unrelated work from running.
Output
Ready to run.
Quick check
What does await suspend?
async function run() { await delay(10); }
Sequential awaits
Two awaits written one after another are sequential when the second operation is started only after the first finishes. This is sometimes correct, but it can unnecessarily increase total latency when the operations are independent.
Output
Ready to run.
Quick check
Why can independent sequential awaits be slower?
const a = await taskA();
const b = await taskB();
Start independent work before awaiting
When operations are independent, create their Promises first and then await them. Promise.all is often the clearest way to express that all results are required.
Output
Ready to run.
Quick check
Which pattern expresses independent operations that should all succeed?
const values = await Promise.all([taskA(), taskB()]);
try/catch around await
Because await turns rejection into a throw at the await point, ordinary try/catch can handle asynchronous failures. Keep the catch boundary close enough to provide useful recovery or context.
Output
Ready to run.
Quick check
Why can ordinary try/catch handle an awaited rejection?
async function load() {
try { await request(); } catch (error) { return "fallback"; }
}
Errors still reject the async function if uncaught
If an async function does not catch a rejection or throw, its returned Promise remains rejected. The caller can choose to catch it with await or with Promise methods.
Output
Ready to run.
Quick check
If an async function rejects and the caller does not attach a handler, what exists?
async function load() { throw new Error("failed"); }
const result = load();
Async iterables and async iterators
An async iterable exposes Symbol.asyncIterator and produces an async iterator. Its next() method returns a Promise for an IteratorResult. This lets a consumer receive values over time.
Output
Ready to run.
Quick check
What does an async iterator next() return?
const iterator = source[Symbol.asyncIterator]();
const result = iterator.next();
for await...of consumes async iterables
for await...of repeatedly awaits the next result from an async iterator. It is designed for values that arrive asynchronously and also knows how to consume synchronous iterables through the language’s async-from-sync adaptation.
Output
Ready to run.
Quick check
Which construct is designed to consume an async iterable one value at a time?
for await (const value of source) {
use(value);
}
Async generator functions
An async generator combines generator-style pause points with Promise-based asynchronous steps. Calling it creates an async iterator; each next() returns a Promise for the next IteratorResult.
Output
Ready to run.
Quick check
What does an async generator call produce?
const pages = async function* () { yield 1; };
const iterator = pages();
Array.fromAsync
Array.fromAsync consumes an async iterable or array-like source and returns a Promise for a new Array. It is useful when you need the complete collection rather than streaming each value yourself.
Output
Ready to run.
Quick check
What does Array.fromAsync return?
const values = await Array.fromAsync(source);
Do not confuse async sequencing with cancellation
Starting an async operation and then abandoning its result does not automatically cancel the underlying work. Cancellation is a separate design concern. Browser APIs commonly use AbortSignal and AbortController; that belongs to the web-platform track rather than the ECMAScript Promise model.
Quick check
Does await automatically cancel the underlying operation when its result is no longer needed?
const result = await operation();
Async cleanup and iterator closing
When an async iteration exits early, the async iterator may receive a return() request so it can perform cleanup. This matters when an async generator owns resources or represents a stream that should be closed.
Output
Ready to run.
Quick check
What can happen when for await...of exits an async iterator early?
for await (const value of source) {
break;
}
Awaiting synchronous values
await also accepts ordinary values. A non-Promise value is treated as an already-available fulfillment value, which makes generic async code convenient when a helper may return either a value or a Promise.
Output
Ready to run.
Quick check
What happens when await receives a non-Promise value?
async function normalize(value) { return await value; }
Async iteration is not automatic parallelism
A for await...of loop consumes one iterator result at a time. If each iteration awaits another independent operation, those operations are naturally sequenced unless you explicitly collect and coordinate their Promises.
Output
Ready to run.
Quick check
Does for await...of automatically run all loop bodies concurrently?
for await (const value of source) { await use(value); }
Output
Ready to run.
Use async functions when Promise-based control flow becomes easier to read as sequential steps.
Await only when you need the result or need the rejection to enter your local error boundary.
Start independent operations before awaiting them when their work can safely overlap.
Use Promise.all when every result is required; choose other combinators by their outcome contract.
Use for await...of when values arrive through the async iterator protocol and should be consumed incrementally.
Do not describe await as blocking the JavaScript thread; it suspends the current async continuation.
Do not assume that dropping a Promise or leaving an async loop cancels arbitrary underlying work.
Keep browser-specific cancellation, fetching, and event-loop scheduling details in the web-platform lessons.
Practice: trace the async flow
Build a small async pipeline that starts two independent operations, combines their results, handles one failure path, and then consumes a short async generator. Explain where execution is suspended and where independent work overlaps.
Specification references
The normative language rules for this lesson come from the living ECMAScript specification. Browser-specific scheduling and cancellation APIs will be covered separately in the web-platform track.
ECMA-262 — Async Function Definitions
ECMA-262 — Async Iterator Interface and Async-from-Sync Iterators
Thanks for your feedback.
Mark this learning resource complete to track your learning progress.