iteratori cu tipuri în TypeScript

Cod asincron și iterare TypeScript 2.3

Un obiect cu metoda [Symbol.iterator] se poate parcurge cu for...of; tipurile sunt Iterable<T> și Iterator<T>.

Sintaxă
class C implements Iterable<T> {
    [Symbol.iterator](): Iterator<T> { ... }
}
for (const x of obiect) { ... }

Ce face iteratori cu tipuri

for...of, operatorul ..., Array.from și destructurarea funcționează cu orice obiect iterabil: un obiect care are metoda [Symbol.iterator](). Metoda returnează un iterator, un obiect cu metoda next() care dă pe rând { value, done }. Tablourile, textele, Map și Set sunt deja iterabile.

TypeScript descrie aceste roluri cu interfețele Iterable<T>, Iterator<T> și IterableIterator<T> (ambele deodată). O clasă proprie devine iterabilă dacă implementează [Symbol.iterator]; cel mai simplu este să scrii metoda ca generator (*[Symbol.iterator]()).

Un parametru de tip Iterable<T> acceptă tablouri, Set-uri, generatori și clase proprii, deci face funcția mai generală decât T[]. Din ES2025, iteratorii predefiniți au și metode ca map, filter, take (tipul IteratorObject), explicate în secțiunea JavaScript.

Exemple

Exemplul 1: Clasă iterabilă scrisă cu generator

class Clasa implements Iterable<string> {
    #elevi: string[] = [];
    adauga(...nume: string[]): this {
        this.#elevi.push(...nume);
        return this;
    }
    *[Symbol.iterator](): Generator<string> {
        yield* this.#elevi.toSorted((a, b) => a.localeCompare(b, "ro"));
    }
}

const c = new Clasa().adauga("Mihai", "Ana", "Ștefan", "Ioana");
for (const elev of c) console.log(elev);
console.log([...c].length, Array.from(c).at(-1));

Rezultat

Ana
Ioana
Mihai
Ștefan
4 Ștefan

Exemplul 2: Iterator scris de mână

Obiectul returnat de next() are tipul IteratorResult<number>.

class Interval implements Iterable<number> {
    constructor(private start: number, private stop: number, private pas = 1) {}

    [Symbol.iterator](): Iterator<number> {
        let curent = this.start;
        const { stop, pas } = this;
        return {
            next(): IteratorResult<number> {
                if (curent > stop) return { done: true, value: undefined };
                const value = curent;
                curent += pas;
                return { done: false, value };
            },
        };
    }
}

console.log([...new Interval(1, 10, 3)]);
let suma = 0;
for (const n of new Interval(1, 100)) suma += n;
console.log(suma);

Rezultat

[ 1, 4, 7, 10 ]
5050

Exemplul 3: Funcție care acceptă orice iterabil

function total(valori: Iterable<number>): number {
    let s = 0;
    for (const v of valori) s += v;
    return s;
}

const preturi = new Map([["pâine", 5], ["lapte", 7.5]]);
console.log(total([1, 2, 3]), total(new Set([10, 10, 20])), total(preturi.values()));

Rezultat

6 30 12.5

Greșeli frecvente

  • Parcurgi cu for...of un obiect simplu ({ a: 1 }). tsc dă Type ‘{ a: number; }’ must have a ‘[Symbol.iterator]()’ method that returns an iterator; folosește Object.entries.
  • Confunzi for...in (cheile unui obiect, ca texte) cu for...of (valorile unui iterabil).

Comenzi înrudite

  • generatori cu tipuri: Funcțiile function* produc valori una câte una cu yield; tipul lor este Generator<T, TReturn, TNext>.
  • bigint și symbol: bigint ține numere întregi oricât de mari; symbol este o valoare unică, folosită de obicei drept cheie de proprietate.
  • implements: O clasă promite că respectă una sau mai multe interfețe; tsc verifică dacă are toți membrii ceruți.
  • array (T[] și Array<T>): Tipul tablourilor: T[] și Array<T> înseamnă același lucru, un tablou cu elemente de tipul T.