DevToolBoxFREE
BlogAdvertise

JavaScript 제너레이터와 이터레이터: 2026년 완전 가이드

12분by DevToolBox

JavaScript 제너레이터(function*)는 yield 키워드로 실행을 일시 중지하고 재개할 수 있는 특수 함수입니다.

제너레이터 기본: function*과 yield

제너레이터 함수는 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

양방향 통신: 표현식으로서의 yield

yield는 단순한 return이 아니라 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*: 다른 iterable에 위임

yield*는 다른 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
    }
}

비동기 제너레이터: 데이터 스트리밍

비동기 제너레이터는 제너레이터와 async/await를 결합하여 스트리밍에 사용합니다.

// 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));
            }
        }
    }
}

실제 패턴

제너레이터는 필요한 데이터만 처리하는 지연 파이프라인을 가능하게 합니다.

// 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);
    }
}

제너레이터 vs 대안

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

모범 사례

  • 지연 시퀀스와 무한 데이터에는 제너레이터, 단발 비동기에는 async/await.
  • 조기 종료에는 return, 정리에는 try/finally 사용.
  • 비동기 제너레이터는 HTTP 스트리밍, SSE, WebSocket에 이상적.
  • 재사용 가능한 파이프라인 헬퍼(map, filter, take)를 제너레이터 함수로 생성.
  • TypeScript: Generator 타입으로 어노테이션.

자주 묻는 질문

제너레이터와 이터레이터의 차이는?

이터레이터는 next()를 가진 객체. 제너레이터는 이터레이터를 자동으로 만들고 관리하는 함수.

제너레이터가 async/await를 대체할 수 있나요?

단발 작업에는 아니오. 스트리밍에는 비동기 제너레이터가 우수.

Iterator 프로토콜이란?

{ value, done }을 반환하는 next()를 가진 객체, [Symbol.iterator]()를 가진 Iterable.

제너레이터는 Redux-Saga에 적합한가요?

네 — Redux-Saga는 테스트 가능한 이펙트에 제너레이터를 사용.

제너레이터를 취소하려면?

generator.return(value)를 호출하거나 for...of에서 break 사용.

도움이 되었나요?

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