# JavaScript Generator

catmaker·2025년 7월 20일

multi-paradigm

목록 보기
1/1
post-thumbnail

Generator가 뭔데?

Iterator 공부하다가 Generator라는 걸 발견했는데... 처음엔 복잡해 보였지만 실제로 써보니 진짜 신기한 기능이더라. 특히 메모리 관리 측면에서 엄청난 차이가 났다.

// 일반 함수 - 모든 값을 한번에 만듦
function normalNumbers() {
  const result = [];
  for (let i = 0; i <= 1000; i++) {
    result.push(i);
  }
  return result; // 메모리에 1001개 모두 저장
}

// Generator - 필요할 때만 하나씩 만듦
function* generatorNumbers() {
  for (let i = 0; i <= 1000; i++) {
    yield i; // 하나씩만 만들어서 반환
  }
}

첫 번째 충격 - 메모리 효율성

기본 문법부터 천천히

function* simpleGenerator(): Generator<number, void, unknown> {
  yield 1;
  yield 2;
  yield 3;
}

const iter = simpleGenerator();
console.log(iter.next()); // { value: 1, done: false }
console.log(iter.next()); // { value: 2, done: false }
console.log(iter.next()); // { value: 3, done: false }
console.log(iter.next()); // { value: undefined, done: true }

핵심 발견: Generator는 필요할 때만 하나씩 값을 만들어준다

  • 메모리에 모든 값을 한번에 올리지 않음
  • done: true가 되면 더 이상 값이 없음
  • yield에서 일시정지했다가 next() 호출하면 재개

실무에서 진짜 유용한 상황들

1. 대용량 데이터 처리

// 10만개 데이터 중에서 처음 10개만 필요한 상황
function* processLargeData() {
  for (let i = 0; i < 100000; i++) {
    // 복잡한 계산이나 API 호출
    const processed = heavyCalculation(i);
    yield processed;
  }
}

// 필요한 만큼만 처리하고 중단
let count = 0;
for (const data of processLargeData()) {
  console.log(data);
  if (++count >= 10) break; // 10개만 처리하고 끝
}

2. 조건부 데이터 생성

function* conditionalGenerator(max: number) {
  for (let i = 1; i <= max; i++) {
    if (i % 2 === 0) {
      yield i; // 짝수만 반환
    }
  }
}

// 사용할 때도 깔끔하게
for (const evenNumber of conditionalGenerator(1000)) {
  console.log(evenNumber); // 2, 4, 6, 8, 10...
}

두 번째 충격 - 양방향 통신

Generator는 단순히 값을 뱉기만 하는 게 아니라 대화할 수 있다는 게 진짜 신기했다.

function* twoWayGenerator(): Generator<string, void, string> {
  const input = yield "사자의 다리 갯수는?";
  yield `받은 답변: ${input}`;
}

const gen = twoWayGenerator();
console.log(gen.next());        // { value: '사자의 다리 갯수는?', done: false }
console.log(gen.next("4개"));   // { value: '받은 답변: 4개', done: false }
console.log(gen.next());        // { value: undefined, done: true }

Generator 타입 이해하기

Generator<YieldType, ReturnType, NextType>
  • YieldType: yield로 반환하는 값의 타입
  • ReturnType: return으로 반환하는 값의 타입
  • NextType: next()로 전달받는 값의 타입

핵심 포인트:

  • 첫 번째 next()는 값을 전달하지 않음
  • 두 번째 next(value)부터 값이 yield 표현식으로 전달됨

함정 주의 - 일회용 특성

Generator를 처음 쓸 때 당황했던 부분이다.

function* simpleGenerator() {
  yield 1;
  yield 2;
  yield 3;
}

const gen = simpleGenerator();

// 첫 번째 사용
for (const value of gen) {
  console.log(value); // 1, 2, 3 출력
}

// 두 번째 사용 (같은 gen)
for (const value of gen) {
  console.log("두 번째:", value); // 아무것도 출력 안됨!
}

해결법: 새로운 Generator 객체를 생성하자

const gen2 = simpleGenerator();
for (const value of gen2) {
  console.log("새로운 gen:", value); // 1, 2, 3 정상 출력
}

무한 Generator의 마법

이게 진짜 Generator의 진가가 드러나는 부분이다.

function* naturals() {
  let n = 1;
  while (true) {
    yield n++; // 무한히 자연수 생성
  }
}

const gen = naturals();
let count = 0;
for (const natural of gen) {
  console.log(natural); // 1, 2, 3, 4, 5
  count++;
  if (count >= 5) break; // 필요한 만큼만 사용
}

핵심:

  • while(true)로 무한 루프를 만들어도 괜찮다
  • 메모리 효율적: 필요한 만큼만 계산
  • 사용하는 쪽에서 언제든 중단 가능

yield* 키워드의 비밀

처음엔 이게 뭔지 몰랐는데, 알고 보니 위임하는 키워드였다.

function* test1() {
  yield 1;
  yield [2, 3]; // 배열 자체를 반환
  yield 4;
}
// 결과: 1, [2, 3], 4

function* test2() {
  yield 1;
  yield* [2, 3]; // 배열을 펼쳐서 각각 반환
  yield 4;
}
// 결과: 1, 2, 3, 4

차이점:

  • yield: 값 자체를 반환
  • yield*: 이터러블을 펼쳐서 각각 반환 (위임)

실전 활용 - reverse Generator

배열을 뒤집는 Generator를 만들어봤는데, 원본 배열을 건드리지 않아서 좋았다.

function* reverse<T>(array: T[]): Generator<T, void, unknown> {
  let idx = array.length; // 3
  while (--idx >= 0) {    // 2, 1, 0 순서로 접근
    yield array[idx];
  }
}

const original = ["A", "B", "C"];
const reversed = reverse(original);

for (const item of reversed) {
  console.log(item); // C, B, A 순서로 출력
}

console.log(original); // ["A", "B", "C"] - 원본 그대로!

동작 원리

  • idx = array.length (3)에서 시작
  • --idx로 먼저 1을 빼고 사용: 2, 1, 0
  • 뒤에서부터 앞으로 접근하여 배열 뒤집기

실무에서 Generator 언제 쓸까?

쓰면 좋은 경우

  • 대용량 데이터 일부만 처리할 때
  • 메모리 효율성이 중요할 때
  • 무한 시퀀스 생성할 때
  • 지연 계산이 필요할 때

굳이 안 써도 되는 경우

  • 작은 데이터 (1000개 미만)
  • 전체 데이터를 다 써야 하는 경우
  • 단순한 배열 조작

TypeScript 설정 주의사항

Generator를 쓸 때 컴파일 에러가 날 수 있다.

// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2015",           // 또는
    "downlevelIteration": true    // 이 옵션 추가
  }
}

성능 비교

// 메모리 사용량 비교
const bigArray = Array.from({length: 1000000}, (_, i) => i);
// → 약 8MB 메모리 사용

function* bigGenerator() {
  for (let i = 0; i < 1000000; i++) {
    yield i;
  }
}
// → 거의 0에 가까운 메모리 사용 (필요할 때만)

마무리

Generator는 처음엔 복잡해 보였지만, 실제로 써보니 메모리 효율성과 지연 계산의 강력함을 느낄 수 있었다.

  1. 메모리 절약: 큰 데이터셋에서 일부만 필요할 때 진가 발휘
  2. 깔끔한 코드: 무한 시퀀스도 자연스럽게 표현
  3. 원본 보존: 배열 reverse처럼 원본을 건드리지 않음
  4. 유연한 제어: 언제든 중단하고 재개 가능

다만 일회용이라는 특성과 작은 데이터에서는 오버엔지니어링이 될 수 있다는 점은 주의해야겠다.

다음엔 async Generator도 공부해봐야지... 비동기 데이터 스트리밍에 쓸 수 있을 것 같다!

profile
'왜?'

0개의 댓글