자바스크립트 교재 · 24편 / 26편

이터레이터·제너레이터 — function*·yield

for...of 의 내부 동작과 게으른 시퀀스의 정석.

고급읽는 시간 7분2026-05-17
제너레이터 함수가 yield 로 값을 하나씩 내놓는 흐름 도식

7편의 for...of 가 어떻게 동작하나? 답은 이터레이터 프로토콜. 모든 "순회 가능한" 객체는 Symbol.iterator 메서드를 가집니다. 24편은 그 메커니즘과 더 강력한 사촌 제너레이터 함수(function*) 까지.

이터레이터 프로토콜 — 한 메서드 한 형식

// 이터레이터 = next() 메서드가 있는 객체
// next() 는 { value, done } 반환

const iter = {
  i: 0,
  next() {
    if (this.i < 3) return { value: this.i++, done: false };
    return { value: undefined, done: true };
  }
};

iter.next();   // { value: 0, done: false }
iter.next();   // { value: 1, done: false }
iter.next();   // { value: 2, done: false }
iter.next();   // { value: undefined, done: true }

이터러블 — Symbol.iterator 가진 객체

// 객체가 Symbol.iterator 메서드를 가지면 for...of 사용 가능
const range = {
  from: 1, to: 5,
  [Symbol.iterator]() {
    let i = this.from;
    const end = this.to;
    return {
      next() {
        return i <= end
          ? { value: i++, done: false }
          : { value: undefined, done: true };
      }
    };
  }
};

for (const n of range) console.log(n);   // 1, 2, 3, 4, 5

// spread 와 구조 분해도 작동
[...range];                                // [1, 2, 3, 4, 5]
const [first, ...rest] = range;            // first=1, rest=[2,3,4,5]

제너레이터 — 위 코드의 짧은 버전

function* rangeGen(from, to) {
  for (let i = from; i <= to; i++) {
    yield i;     // 멈췄다가 다시 호출되면 여기부터
  }
}

for (const n of rangeGen(1, 5)) console.log(n);   // 1, 2, 3, 4, 5

// 제너레이터 함수 — function* 키워드
// 호출하면 즉시 실행 안 됨 — 제너레이터 객체(이터러블+이터레이터) 반환
const g = rangeGen(1, 3);
g.next();   // { value: 1, done: false }
g.next();   // { value: 2, done: false }
g.next();   // { value: 3, done: false }
g.next();   // { value: undefined, done: true }

제너레이터의 핵심. 일반 함수는 한 번 실행되면 끝까지. 제너레이터는 yield 에서 멈추고, 다음 next() 때 그 자리부터 재개. 함수 실행을 "일시정지·재개" 할 수 있는 유일한 도구.

무한 시퀀스 — 게으른 평가

function* fibo() {
  let [a, b] = [0, 1];
  while (true) {
    yield a;
    [a, b] = [b, a + b];
  }
}

const f = fibo();
for (let i = 0; i < 10; i++) console.log(f.next().value);
// 0, 1, 1, 2, 3, 5, 8, 13, 21, 34

// 또는 take 헬퍼
function* take(iter, n) {
  let i = 0;
  for (const v of iter) {
    if (i++ >= n) return;
    yield v;
  }
}

[...take(fibo(), 5)];   // [0, 1, 1, 2, 3]

yield* — 다른 이터러블 위임

function* nums() {
  yield 1;
  yield 2;
}

function* letters() {
  yield "a";
  yield "b";
}

function* combined() {
  yield* nums();     // nums 의 값을 모두 yield
  yield "---";
  yield* letters();
}

[...combined()];   // [1, 2, "---", "a", "b"]

제너레이터로 코루틴 — 양방향 통신

function* echo() {
  const x = yield "첫 번째 응답";
  console.log("받음:", x);
  const y = yield "두 번째 응답";
  console.log("받음:", y);
}

const g = echo();
g.next();          // { value: "첫 번째 응답", done: false }
g.next("Hi");      // "받음: Hi"  → { value: "두 번째 응답", done: false }
g.next("World");   // "받음: World"  → { value: undefined, done: true }

호출자가 next() 인자로 제너레이터에 값을 다시 줌. 옛 redux-saga 가 이 메커니즘으로 비동기 흐름을 구현했습니다.

async iterator · for await...of

// 비동기 스트림 — next() 가 Promise 반환
async function* fetchPages(url) {
  let next = url;
  while (next) {
    const res = await fetch(next);
    const page = await res.json();
    yield page.items;
    next = page.nextUrl;
  }
}

// for await...of 로 소비
for await (const items of fetchPages("/api/posts?page=1")) {
  for (const item of items) console.log(item);
}

// LLM 스트리밍·페이지네이션·파일 청크 등에 자연스러움

이터러블 → 배열·Set·Map

// 이터러블이면 Array.from·spread·new Set·new Map 모두 작동
function* gen() { yield 1; yield 2; yield 3; }

Array.from(gen());     // [1, 2, 3]
[...gen()];            // [1, 2, 3]
new Set(gen());        // Set(3) {1, 2, 3}
new Map([["a", 1], ["b", 2]][Symbol.iterator]());   // Map

제너레이터의 일상 용도 — 4가지

  1. 지연 평가 — 큰/무한 시퀀스를 메모리에 다 안 만들고 필요할 때만.
  2. 커스텀 이터러블 — 트리 순회·페이지네이션 등.
  3. async iterator — 스트림 (LLM·SSE·DB 커서).
  4. 코루틴 — 옛 saga·테스트 모킹. 현대 코드는 async/await 가 대부분 대체.

제너레이터의 비용. 일반 함수보다 약간 느림 (상태 보존). 한 번에 모두 배열로 쓸 수 있는 작은 데이터면 그냥 배열이 단순. 무한·매우 큰 시퀀스·중간에 멈춰야 할 때 진가.

한 줄 정리

  • 이터러블 = [Symbol.iterator]() 메서드 → 이터레이터 반환.
  • 이터레이터 = next() 메서드 → {value, done} 반환.
  • 제너레이터(function*) 는 이터러블+이터레이터 한 번에.
  • yield 로 일시정지·재개.
  • for await...of + async function* = 비동기 스트림 표준.

25편 — 디자인 패턴 5종

Singleton·Observer·Factory·Module·Strategy — JS 식 구현.

📚 쉽게 배우는 자바스크립트 교재
이전: 23편 프로토타입 · 현재: 24편 (고급) · 다음 → 25편 디자인 패턴 · 진행: 24/26

© 2026 주나이테크(주) @JUNAITECH