DevToolBoxFREE
BlogAdvertise

Generatory i Iteratory JavaScript: Kompletny Przewodnik 2026

12 minby DevToolBox

Generatory JavaScript (function*) to specjalne funkcje, które mogą być wstrzymywane i wznawiane za pomocą yield.

Podstawy generatorów: function* i yield

Funkcja generatora zwraca obiekt generatora implementujący protokół Iterator.

// Generator function syntax: function* with yield
function* counter(start = 0) {
    let i = start;
    while (true) {
        yield i++;    // pauses here, returns i, resumes on next()
    }
}

const gen = counter(1);
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }

// Finite generator
function* range(start, end, step = 1) {
    for (let i = start; i < end; i += step) {
        yield i;
    }
}

// for...of automatically calls .next() and stops at done: true
for (const num of range(0, 10, 2)) {
    console.log(num); // 0, 2, 4, 6, 8
}

// Spread operator works with generators
const nums = [...range(1, 6)]; // [1, 2, 3, 4, 5]

// Destructuring works too
const [a, b, c] = range(10, 20); // a=10, b=11, c=12

Komunikacja dwukierunkowa: yield jako wyrażenie

yield to nie tylko return — to wyrażenie, które otrzymuje wartość przez next(value).

// Generators support two-way communication
// yield receives values via next(value)
function* calculator() {
    let result = 0;
    while (true) {
        const input = yield result;  // pauses and sends result; receives input
        if (input === null) break;
        result += input;
    }
    return result;
}

const calc = calculator();
calc.next();        // start: { value: 0, done: false }
calc.next(10);      // add 10: { value: 10, done: false }
calc.next(5);       // add 5: { value: 15, done: false }
calc.next(null);    // stop: { value: 15, done: true }

// Generator as stateful iterator
function* idGenerator(prefix = 'id') {
    let id = 1;
    while (true) {
        const reset = yield `${prefix}-${id}`;
        if (reset) {
            id = 1;
        } else {
            id++;
        }
    }
}

const ids = idGenerator('user');
console.log(ids.next().value);       // 'user-1'
console.log(ids.next().value);       // 'user-2'
console.log(ids.next(true).value);   // 'user-1' (reset)
console.log(ids.next().value);       // 'user-2'

yield*: delegowanie do innych iterowalnych

yield* deleguje do innego iterable.

// yield* — delegate to another iterable
function* innerGen() {
    yield 'a';
    yield 'b';
    yield 'c';
}

function* outerGen() {
    yield 1;
    yield* innerGen();    // delegate: yields 'a', 'b', 'c'
    yield* [4, 5, 6];    // works with any iterable
    yield 7;
}

console.log([...outerGen()]); // [1, 'a', 'b', 'c', 4, 5, 6, 7]

// Practical: flatten nested arrays
function* flatten(arr) {
    for (const item of arr) {
        if (Array.isArray(item)) {
            yield* flatten(item); // recursive delegation
        } else {
            yield item;
        }
    }
}

const nested = [1, [2, [3, 4], 5], [6, 7]];
console.log([...flatten(nested)]); // [1, 2, 3, 4, 5, 6, 7]

// Tree traversal with yield*
function* walkTree(node) {
    yield node.value;
    for (const child of node.children ?? []) {
        yield* walkTree(child); // depth-first traversal
    }
}

Generatory asynchroniczne: streaming danych

Generatory asynchroniczne łączą generatory z async/await dla streamingu.

// Async generators: async function* with yield
async function* streamLines(url) {
    const response = await fetch(url);
    const reader = response.body.getReader();
    const decoder = new TextDecoder();
    let buffer = '';

    while (true) {
        const { done, value } = await reader.read();
        if (done) {
            if (buffer) yield buffer;
            break;
        }
        buffer += decoder.decode(value, { stream: true });
        const lines = buffer.split('\n');
        buffer = lines.pop() ?? '';
        for (const line of lines) {
            yield line;   // yield each complete line
        }
    }
}

// Usage with for await...of
async function processCSV(url) {
    let lineNumber = 0;
    for await (const line of streamLines(url)) {
        lineNumber++;
        if (lineNumber === 1) continue; // skip header
        const [name, score] = line.split(',');
        console.log(`${name}: ${score}`);
    }
}

// SSE (Server-Sent Events) as async generator
async function* sseStream(url) {
    const response = await fetch(url);
    const reader = response.body.getReader();
    const decoder = new TextDecoder();

    for await (const chunk of readChunks(reader)) {
        const text = decoder.decode(chunk);
        for (const line of text.split('\n')) {
            if (line.startsWith('data: ')) {
                yield JSON.parse(line.slice(6));
            }
        }
    }
}

Wzorce z praktyki

Generatory umożliwiają leniwe potoki przetwarzające tylko potrzebne dane.

// Real-world: infinite scroll with generator
function* paginator(fetchPage) {
    let page = 1;
    let hasMore = true;

    while (hasMore) {
        const { items, totalPages } = yield fetchPage(page);
        hasMore = page < totalPages;
        page++;
    }
}

// Lazy pipeline with generators
function* map(iterable, fn) {
    for (const item of iterable) {
        yield fn(item);
    }
}

function* filter(iterable, predicate) {
    for (const item of iterable) {
        if (predicate(item)) yield item;
    }
}

function* take(iterable, n) {
    let count = 0;
    for (const item of iterable) {
        if (count++ >= n) break;
        yield item;
    }
}

// Lazy pipeline — no intermediate arrays created!
const first10EvenSquares = [
    ...take(
        filter(
            map(range(1, Infinity), x => x * x),
            x => x % 2 === 0
        ),
        10
    )
];
// [4, 16, 36, 64, 100, 144, 196, 256, 324, 400]

// Observable-like: cancelable async iteration
async function* withTimeout(asyncIterable, timeoutMs) {
    const timeout = setTimeout(() => {
        throw new Error('Stream timed out');
    }, timeoutMs);
    try {
        for await (const item of asyncIterable) {
            yield item;
        }
    } finally {
        clearTimeout(timeout);
    }
}

Generatory vs alternatywy

FeatureGeneratorasync/awaitPromiseObservable
Infinite sequencesPerfectNoNoYes
Lazy evaluationYes (pull-based)NoNoYes (push)
BackpressureNatural (pull)NoNoYes
Streaming asyncasync function*NoNoYes (RxJS)
Two-way commsyield expressionNoNoNo
Browser supportES2015+ (all)ES2017+ES2015+Requires RxJS

Najlepsze praktyki

  • Generatory dla leniwych sekwencji, async/await dla jednorazowych operacji.
  • Używać return dla wczesnego wyjścia, try/finally dla czyszczenia.
  • Generatory asynchroniczne idealne dla HTTP streaming, SSE, WebSocket.
  • Tworzyć wielokrotnego użytku helpery pipeline (map, filter, take).
  • TypeScript: adnotować typem Generator dla bezpieczeństwa typów.

FAQ

Różnica między generatorem a iteratorem?

Iterator to każdy obiekt z next(). Generator automatycznie tworzy iterator i nim zarządza.

Czy generatory mogą zastąpić async/await?

Dla jednorazowych operacji nie. Do streamingu generatory asynchroniczne są lepsze.

Co to protokół Iterator?

Obiekt z next() zwracający { value, done } i [Symbol.iterator]() dla iterable.

Generatory dobre dla Redux-Saga?

Tak — Redux-Saga używa ich dla testowalnych efektów.

Jak anulować generator?

Wywołać generator.return(value) lub użyć break w for...of.

Czy to było pomocne?

Stay Updated

Get weekly dev tips and new tool announcements.

No spam. Unsubscribe anytime.

Partner Picks

Sponsor this article

Place your product next to this developer topic with tracked clicks.

Ask about article sponsorship

This site uses cookies for analytics and to display ads. By continuing to browse, you agree. Privacy Policy