Перейти к основному содержимому

Генераторы и итераторы в 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*
Пагинация APIasync function*
Простой массивfor...of / map19.md
RxJS-потокиObservable (вне TS core)

Частые ошибки

ОшибкаЧто делать
Забыли * в function*синтаксическая ошибка
Путаница Generator и Iterableреализуйте Symbol.iterator
Бесконечный генератор без breakусловие выхода
for...of на async genтолько for await...of

Практика

  1. Напишите range(from, to): Generator<number>.
  2. Реализуйте Counter как Iterable.
  3. Сделайте async function* с тремя страницами mock API.
  4. Сравните память: [...range(1, 1_000_000)] vs цикл с break на 100.
  5. Типизируйте возврат генератора явно.

Смежные статьи