generatori cu tipuri în TypeScript

Cod asincron și iterare TypeScript 3.6

Funcțiile function* produc valori una câte una cu yield; tipul lor este Generator<T, TReturn, TNext>.

Sintaxă
function* gen(): Generator<TYield, TReturn, TNext> { yield valoare; }
async function* agen(): AsyncGenerator<T> { ... }

Ce face generatori cu tipuri

Un generator este o funcție scrisă cu function* care se poate opri la fiecare yield și continua mai târziu. Apelul ei nu rulează codul imediat, ci returnează un obiect pe care îl parcurgi cu for...of, cu ... sau cu next(). Este util pentru șiruri lungi sau infinite, calculate doar când e nevoie.

Tipul este Generator<T, TReturn, TNext>: T este tipul valorilor din yield, TReturn tipul valorii din return (implicit void/any), iar TNext tipul valorii primite prin next(valoare). De obicei ajunge Generator<T> sau poți lăsa tipul să fie dedus. Funcțiile async function* au tipul AsyncGenerator<T> și se parcurg cu for await...of.

Pentru obiecte proprii care se pot parcurge cu for...of vezi iteratori. Opțiunea strictBuiltinIteratorReturn (parte din strict) dă tipul corect, undefined, la valoarea de final a iteratorilor predefiniți.

Exemple

Exemplul 1: Șir calculat la cerere

Generatorul este infinit, dar fiecare valoare se calculează abia când bucla o cere.

function* fibonacci(): Generator<number> {
    let [a, b] = [0, 1];
    while (true) {
        yield a;
        [a, b] = [b, a + b];
    }
}

const primele: number[] = [];
for (const n of fibonacci()) {
    if (n > 100) break;
    primele.push(n);
}
console.log(primele.join(" "));

Rezultat

0 1 1 2 3 5 8 13 21 34 55 89

Exemplul 2: Toate cele trei tipuri: yield, return și next

function* casa(): Generator<string, number, number> {
    let total = 0;
    while (true) {
        const pret: number = yield `total: ${total} lei`;
        if (pret === 0) return total;
        total += pret;
    }
}

const g = casa();
console.log(g.next(0).value);       // primul next pornește generatorul
console.log(g.next(12.5).value);
console.log(g.next(7).value);
const final = g.next(0);
console.log(final.done, final.value);

Rezultat

total: 0 lei
total: 12.5 lei
total: 19.5 lei
true 19.5

Exemplul 3: Generator asincron

async function* pagini(total: number): AsyncGenerator<string[]> {
    const elevi = ["Ana", "Mihai", "Ioana", "Andrei", "Maria"];
    for (let i = 0; i < elevi.length; i += total) {
        await new Promise((r) => setTimeout(r, 10));     // simulează o cerere la server
        yield elevi.slice(i, i + total);
    }
}

let numar = 1;
for await (const pagina of pagini(2)) {
    console.log(`pagina ${numar++}:`, pagina);
}

Rezultat

pagina 1: [ 'Ana', 'Mihai' ]
pagina 2: [ 'Ioana', 'Andrei' ]
pagina 3: [ 'Maria' ]

Greșeli frecvente

  • Apelezi generatorul și te aștepți să ruleze: fibonacci() singur nu face nimic până nu îl parcurgi.
  • Parcurgi același obiect generator de două ori. După prima parcurgere este epuizat; apelează din nou funcția.

Comenzi înrudite

  • iteratori cu tipuri: Un obiect cu metoda [Symbol.iterator] se poate parcurge cu for…of; tipurile sunt Iterable<T> și Iterator<T>.
  • async/await cu tipuri: O funcție async returnează mereu Promise<T>; await scoate valoarea de tip T din Promise.
  • Promise<T>: Tipul unei valori care va fi disponibilă mai târziu; T este tipul rezultatului, nu și al erorii.
  • strictBuiltinIteratorReturn: Iteratorii obținuți din Array, Map, Set și String au valoarea finală (done: true) de tip undefined, nu any.