Async, Iterators and Generators
|
This section documents the current TypeScript release line as published at the official TypeScript documentation, which is the reference these pages are written and verified against. No specific patch version is pinned. This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production, since TypeScript iterates quickly. This section’s bibliography lists the reference material consulted while preparing these pages. |
The runtime semantics are exactly JavaScript’s — see Asynchronous JavaScript and
Iterators and Generators. This page is about the types: the generic wrappers
(Promise<T>, Generator<T, TReturn, TNext>), how await and for…of propagate element types, and the
target / lib settings each feature needs. The official
Iterators and Generators
handbook chapter is the companion reference.
Promise<T>
Promise<T> is the generic that types an eventual value: T is what the promise resolves to. A rejection
carries no type — it surfaces as any (or unknown with useUnknownInCatchVariables) at the catch.
See Generics for how T threads through .then.
function fetchUser(id: number): Promise<{ id: number; name: string }> {
return fetch(`/api/users/${id}`).then((res) => res.json());
}
fetchUser(1).then((user) => {
user.name; // string -- T flows through .then
});
Typing new Promise
The executor’s resolve is untyped unless you supply the type argument, so always write new Promise<T>.
Without it TypeScript infers Promise<unknown>.
const ready = new Promise<string>((resolve, reject) => {
setTimeout(() => resolve("done"), 100); // resolve now requires a string
// resolve(42); // Error: number is not assignable to string
});
async functions always return a promise
An async function is always Promise-returning. You annotate the inner type and TypeScript wraps it; a
bare value in a return is fine. The return annotation itself must be a Promise<…> (or any).
async function load(): Promise<number> {
return 42; // wrapped -> Promise<number>
}
// async function bad(): number {} // Error: an async function's return type must be Promise<T>
await and Awaited<T>
await unwraps a Promise<T> to T, recursively (a promise of a promise collapses). Awaited<T> is the
utility type that models this and is what the compiler uses internally.
const value: number = await load(); // Awaited<Promise<number>> = number
type A = Awaited<Promise<string>>; // string
type B = Awaited<Promise<Promise<boolean>>>; // boolean -- unwraps recursively
type C = Awaited<number>; // number -- non-promises pass through
See Awaited<Type> in the
utility types handbook, and Utility Types.
Promise.all / Promise.allSettled tuple typing
Passed an array literal, both methods infer a tuple type and map each element through Awaited<T>, so a
destructuring assignment keeps every position’s precise type.
const [user, count, label] = await Promise.all([
fetchUser(1), // Promise<{ id: number; name: string }>
Promise.resolve(42), // Promise<number>
"literal" as const, // not a promise -- passed through unchanged
]);
// user: { id: number; name: string }, count: number, label: "literal"
const settled = await Promise.allSettled([fetchUser(1), fetchUser(2)]);
// settled: [PromiseSettledResult<User>, PromiseSettledResult<User>]
for (const r of settled) {
if (r.status === "fulfilled") {
r.value; // User -- narrowed by the discriminant
} else {
r.reason; // any
}
}
PromiseSettledResult<T> is a discriminated union on status. Promise.allSettled needs lib to include
ES2020.Promise (automatic at target ES2020+); Promise.any and AggregateError need ES2021.
Top-level await
At module scope you can await without an async wrapper. It requires module set to es2022, esnext,
system, node16, node18, or nodenext, and target es2017 or later.
// config.ts -- a module, not inside any function
const res = await fetch("/config.json");
export const config: { port: number } = await res.json();
async over callbacks, to improve type flow
A Node-style callback splits the result across parameter positions ((err, data) ⇒ …) and the type is
lost at every boundary. A Promise carries T through await, .then, and Promise.all, so wrapping a
callback API once at the edge keeps the rest of the code inside the checked async data flow.
import { readFile } from "node:fs";
// Callback-to-promise wrapper: annotate the resolve type, reject the error.
function readFileAsync(path: string): Promise<Buffer> {
return new Promise<Buffer>((resolve, reject) => {
readFile(path, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
}
const text = (await readFileAsync("./notes.txt")).toString("utf8"); // Buffer -> string
Node’s util.promisify does this generically, with typed overloads that recover Promise<T> from the
callback’s data parameter — prefer it when the API follows the (err, value) convention.
Iterators and iterables
Iterable<T> has a [Symbol.iterator]() method returning an Iterator<T>; Iterator<T, TReturn, TNext>
has next() returning IteratorResult<T> (a union of \{ value: T; done: false } and
\{ value: TReturn; done: true }). IterableIterator<T> is both at once — an iterator that returns itself
from [Symbol.iterator] — which is what generators and the built-in collection iterators produce.
const range: Iterable<number> = {
[Symbol.iterator](): Iterator<number> {
let i = 0;
return {
next(): IteratorResult<number> {
return i < 3 ? { value: i++, done: false } : { value: undefined, done: true };
},
};
},
};
for (const n of range) {
n; // number
}
for…of typing
The loop variable takes the element type of the Iterable<T> being consumed: T[] yields T, a
Map<K, V> yields [K, V], a Set<T> yields T, and a string yields string (one code point at a
time). Iterating a plain object is still a type error — objects are not iterable.
const scores = new Map<string, number>([["ada", 10]]);
for (const [name, score] of scores) {
name; // string
score; // number
}
target, downlevelIteration, and lib
-
libmust include the iterable declarations (ES2015.Iterable) forSymbol.iteratorto be typed — automatic whentargetises2015/es6or later. -
When
targetises5/es3,for…of, spread, and array destructuring over an arbitrary iterable (aMap,Set, or generator) only down-compile correctly with"downlevelIteration": true, which emits helper code that callsSymbol.iterator. Without it, only array/string iteration is lowered (by index). -
At
targetes2015+ native iteration is emitted anddownlevelIterationis unnecessary.
{ "compilerOptions": { "target": "es5", "downlevelIteration": true,
"lib": ["es5", "es2015.iterable", "dom"] } }
Generator functions
A function* returns a Generator<T, TReturn, TNext>: T is each yield`ed value, `TReturn the final
return value, and TNext the type a yield expression evaluates to (what the caller passes to
next(x)). The return annotation must be Generator<…> or IterableIterator<…>. Because a generator
object is an IterableIterator<T>, it drops straight into for…of and spread — where the TReturn
value is not visited.
function* countUp(limit: number): Generator<number, string, void> {
let i = 0;
while (i < limit) {
yield i++;
}
return "done"; // TReturn -- seen by g.next(), not by for...of
}
const g = countUp(3);
g.next(); // { value: 0, done: false }
for (const n of countUp(3)) {
n; // number
}
TNext types the two-way channel: the value handed to next() becomes the result of the paused yield.
function* accumulator(): Generator<number, void, number> {
let total = 0;
while (true) {
const add: number = yield total; // supplied by next(add)
total += add;
}
}
const acc = accumulator();
acc.next(); // { value: 0, done: false } -- first next() has no yield to feed
acc.next(10); // { value: 10, done: false }
acc.next(5); // { value: 15, done: false }
Async iterators
AsyncIterable<T> has [Symbol.asyncIterator]() returning an AsyncIterator<T> whose next() returns
Promise<IteratorResult<T>>. for await…of consumes any AsyncIterable<T> (and also a sync iterable of
promises), awaiting each step; the loop variable is T. An async function* returns an
AsyncGenerator<T, TReturn, TNext>, which is an AsyncIterableIterator<T>.
async function* streamPages(url: string): AsyncGenerator<string[], void, void> {
let next: string | null = url;
while (next) {
const res = await fetch(next);
const page: { items: string[]; next: string | null } = await res.json();
yield page.items; // T = string[]
next = page.next;
}
}
for await (const items of streamPages("/api/logs")) {
items; // string[]
}
Requires lib to include ES2018.AsyncIterable (automatic at target es2018+); for await at module
top level needs the same module / target settings as top-level await.
using / await using and explicit resource management
TypeScript 5.2 implemented the TC39 explicit resource management proposal. using x = … binds a resource
whose [Symbol.dispose]() runs when the enclosing block exits — including on throw — like a scoped
try/finally. await using x = … instead awaits [Symbol.asyncDispose](). The initializer must be
Disposable (or AsyncDisposable for await using); DisposableStack / AsyncDisposableStack aggregate
several resources.
function openHandle(path: string): Disposable & { read(): string } {
// ...open the file...
return {
read: () => "contents",
[Symbol.dispose]() { /* close the file */ },
};
}
{
using h = openHandle("./data");
h.read();
} // h[Symbol.dispose]() called here, even if read() threw
class Pool implements AsyncDisposable {
async [Symbol.asyncDispose](): Promise<void> { /* drain connections */ }
}
async function run(): Promise<void> {
await using pool = new Pool();
} // await pool[Symbol.asyncDispose]() on exit
lib / target requirements: the Disposable, AsyncDisposable, and Symbol.dispose /
Symbol.asyncDispose declarations come from the esnext.disposable lib. Add it explicitly unless target
already pulls it in ("lib": ["es2022", "esnext.disposable", "dom"], or just "lib": ["esnext"]). When
down-levelling, TypeScript emits a Symbol.for-based fallback for the disposal symbols, but the runtime
still needs Symbol support. See
the
TypeScript 5.2 release notes.
See also
-
Asynchronous JavaScript — promises,
async/await, and event-loop semantics at runtime. -
Iterators and Generators — the iteration protocols and
function*in plain JavaScript. -
Generics — how
Promise<T>and the iterator types propagate their type parameters. -
Utility Types —
Awaited<T>and the other standard type-level helpers.