2 min read

TypeScript Iterators and Generators

Iterators and Generators are powerful features in JavaScript (ES6) that allow you to customize how data is traversed. TypeScript adds type safety to these patterns.

1. Iterators

An object is an Iterator if it knows how to access items from a collection one at a time, while keeping track of its current position within that sequence. It provides a next() method that returns { value: T, done: boolean }.

An object is Iterable if it has a method keyed by Symbol.iterator that returns an Iterator.

Built-in Iterables

Arrays, Strings, Maps, and Sets are built-in iterables.

let numbers = [1, 2, 3];
for (let num of numbers) {
    console.log(num);
}

Custom Iterator

You can implement the Iterable interface to make your own objects usable in for...of loops.

class Counter implements Iterable<number> {
    constructor(public limit: number) {}

    [Symbol.iterator](): Iterator<number> {
        let count = 0;
        let limit = this.limit;

        return {
            next(): IteratorResult<number> {
                if (count < limit) {
                    return { done: false, value: ++count };
                } else {
                    return { done: true, value: null as any };
                }
            }
        }
    }
}

let counter = new Counter(3);
for (let i of counter) {
    console.log(i); // 1, 2, 3
}

2. Generators

Generators provide a much easier way to create iterators. Instead of managing state and implementing next() manually, you write a function that can pause execution using yield.

Syntax

Use an asterisk * after the function keyword.

function* simpleGenerator() {
    yield 1;
    yield 2;
    yield 3;
}

let iterator = simpleGenerator();

console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }

3. Typing Generators

TypeScript provides the Generator<T, TReturn, TNext> generic type.

  • T: The type of values yielded by yield.
  • TReturn: The type of the value returned by return (optional, defaults to void).
  • TNext: The type of the value passed to next() (optional, defaults to unknown).
function* generateId(): Generator<number, string, boolean> {
    let i = 0;
    while (true) {
        // 'reset' captures the value passed to next(val)
        const reset = yield i++; 
        if (reset) {
            i = 0;
        }
        if (i > 10) {
            return "Limit reached";
        }
    }
}

const gen = generateId();
console.log(gen.next().value); // 0
console.log(gen.next().value); // 1
console.log(gen.next(true).value); // 0 (reset)

4. Async Iterators and Generators

You can also iterate over data that comes asynchronously (like streams).

Async Generators

Use async function* and await inside.

async function* asyncGenerator() {
    let i = 0;
    while (i < 3) {
        await new Promise(resolve => setTimeout(resolve, 1000)); // Wait 1s
        yield i++;
    }
}

(async () => {
    for await (const num of asyncGenerator()) {
        console.log(num);
    }
})();

programming/javascript/typescript/typescript