Генераторы и итераторы в TypeScript
Дальше: Асинхронность · Дженерики · Циклы
Генераторы (function*) возвращают ленивую последовательность через yield. TypeScript типизирует их как Generator<T> и AsyncGenerator<T>. Полезно для потоков данных, пагинации и обхода без загрузки всего массива в память.
Маршрут: циклы → Асинхронность → генераторы.
Runtime и протокол итерации — JS 23 (если есть в разделе) / итераторы ECMAScript.
Generator<T>
function* range(from: number, to: number): Generator<number> {
for (let i = from; i <= to; i += 1) {
yield i;
}
}
for (const n of range(1, 3)) {
console.log(n);
}
Разбор:
function*возвращает итерируемый объект с методомnext().- Тип yield —
number; return type генератора —Generator<number, void, unknown>(третий параметр — типyield*).
Явный тип возврата
function* ids(): Generator<string, void, undefined> {
let n = 0;
while (true) {
yield `id-${n += 1}`;
}
}
Iterable и Symbol.iterator
class Counter implements Iterable<number> {
constructor(private readonly max: number) {}
*[Symbol.iterator](): Generator<number> {
for (let i = 1; i <= this.max; i += 1) {
yield i;
}
}
}
for (const n of new Counter(3)) {
console.log(n);
}
Разбор:
for...ofвызываетSymbol.iterator.- Можно реализовать итератор вручную (
next(): IteratorResult<T>), генератор проще.
Делегирование: yield*
function* concat(
a: Iterable<number>,
b: Iterable<number>,
): Generator<number> {
yield* a;
yield* b;
}
Разбор:
yield*перебирает другую последовательность.
AsyncGenerator и for await...of
type User = { id: string; name: string };
type Page<T> = { items: T[]; hasMore: boolean };
async function* fetchAllUsers(baseUrl: string): AsyncGenerator<User> {
let page = 1;
let hasMore = true;
while (hasMore) {
const res = await fetch(`${baseUrl}?page=${page}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const body = (await res.json()) as Page<User>;
for (const user of body.items) {
yield user;
}
hasMore = body.hasMore;
page += 1;
}
}
async function consume(): Promise<void> {
for await (const user of fetchAllUsers("/api/users")) {
console.log(user.name);
}
}
Разбор:
- Генератор отдаёт по одному
User, хотя API отвечает страницами — память не забивается всем списком сразу. AsyncGenerator<User>— каждыйyieldпослеawaitвнутриasync function*.- Для production JSON лучше
unknown+ guard — 17.md, 6.md; здесьas Page<User>для краткости. - Тот же цикл в 13.md — связка
for await...ofс постраничным API.
Типизация next и ранний выход
function* gen(): Generator<number> {
yield 1;
yield 2;
return 99;
}
const g = gen();
console.log(g.next()); // { value: 1, done: false }
console.log(g.return(0)); // завершение
Разбор:
returnв генераторе даёт{ done: true, value: 99 }.- В прикладном коде чаще используют
breakвfor...of.
Когда использовать
| Сценарий | Подход |
|---|---|
| Ленивый обход большого списка | function* |
| Пагинация API | async function* |
| Простой массив | for...of / map — 19.md |
| RxJS-потоки | Observable (вне TS core) |
Частые ошибки
| Ошибка | Что делать |
|---|---|
Забыли * в function* | синтаксическая ошибка |
Путаница Generator и Iterable | реализуйте Symbol.iterator |
Бесконечный генератор без break | условие выхода |
for...of на async gen | только for await...of |
Практика
- Напишите
range(from, to): Generator<number>. - Реализуйте
CounterкакIterable. - Сделайте
async function*с тремя страницами mock API. - Сравните память:
[...range(1, 1_000_000)]vs цикл сbreakна 100. - Типизируйте возврат генератора явно.