[JavaScript] 제너레이터 함수

제너레이터(generator)는 함수 실행을 중단했다가 필요할 때마다 재개할 수 있도록 해 주는 특별한 함수입니다. 일반 함수는 한 번 호출되면 단 하나의 값(또는 아무 값도) 반환하지만, 제너레이터 함수는 여러 값을 필요에 따라 “yield”하여 순차적으로 반환할 수 있습니다.

제너레이터 함수와 기본 사용법

제너레이터 함수는 function* 문법으로 정의하며, 호출 시 실행을 시작하지 않고 제너레이터 객체를 반환합니다. 이 객체는 .next() 메서드를 통해 제어할 수 있으며, 각 호출마다 함수 내부의 다음 yield 문까지 실행됩니다.

function* generateNumbers() {
  yield 10;
  yield 20;
  return 30;
}

const gen = generateNumbers();

console.log(gen.next()); // { value: 10, done: false }
console.log(gen.next()); // { value: 20, done: false }
console.log(gen.next()); // { value: 30, done: true }
console.log(gen.next()); // { value: undefined, done: true }
  • 첫 번째 next() 호출 시, yield 10에서 실행이 멈추고 { value: 10, done: false }를 반환합니다.
  • 마지막 return 30 문은 이터레이션 종료 시의 결과로, for‑of 반복문 등에서는 무시됩니다.

for...of 루프 및 스프레드 연산자를 활용한 효율적인 이터레이션

제너레이터는 이터러블 객체이므로, for..of나 전개 연산자(...)를 사용해 손쉽게 값을 순회할 수 있습니다.

function* generateNumbers() {
  yield 10;
  yield 20;
  yield 30;
}

const numbers = [...generateNumbers()];
console.log(numbers); // [10, 20, 30]

for (const num of generateNumbers()) {
  console.log(num); // 10, 20, 30 순차 출력
}

제너레이터 컴포지션 (yield*)

제너레이터 컴포지션은 이터러블 객체 (배열, 문자열, 제너레이터 등) 를 대상으로 작동하며, yield* 키워드를 사용하여 다른 제너레이터의 실행 결과를 현재 제너레이터에 위임하는 강력한 기능입니다. 이를 통해 여러 개의 제너레이터를 조합하고 재사용하여 복잡한 제너레이터 로직을 모듈화하고, 코드의 구조를 개선할 수 있습니다.

// 지정 범위의 숫자를 생성하는 제너레이터
function* range(start, end) {
  for (let i = start; i <= end; i++) {
    yield i;
  }
}

// 숫자와 알파벳의 문자 코드 값을 순차적으로 산출하는 제너레이터
function* generateCharCodes() {
  yield* range(48, 57);   // 0-9 (문자 코드 48~57)
  yield* range(65, 90);   // A-Z (문자 코드 65~90)
  yield* range(97, 122);  // a-z (문자 코드 97~122)
}

let result = '';
for (let code of generateCharCodes()) {
  result += String.fromCharCode(code);
}
console.log(result); 
// 결과: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
// 숫자를 생성하는 제너레이터
function* generateDigits() {
    yield* [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; // 배열 이터러블 위임
}

// 영문 대문자를 생성하는 제너레이터
function* generateUppercaseLetters() {
    yield* "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; // 문자열 이터러블 위임
}

// 영문 소문자를 생성하는 제너레이터
function* generateLowercaseLetters() {
    yield* function*() { // 익명 제너레이터 함수 위임
        for (let i = 97; i <= 122; i++) {
            yield String.fromCharCode(i);
        }
    }();
}

// 숫자, 대문자, 소문자를 결합한 문자 코드 제너레이터
function* generateCharacterCodes() {
    yield* generateDigits(); // 숫자 제너레이터 위임
    yield* generateUppercaseLetters(); // 대문자 제너레이터 위임
    yield* generateLowercaseLetters(); // 소문자 제너레이터 위임
}

let characters = '';
for (const char of generateCharacterCodes()) {
    characters += char;
}
console.log("문자 코드 조합:", characters); 
// 출력: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"

제너레이터와 양방향 데이터 교환

제너레이터는 단순히 값을 내보내는(yield) 것뿐 아니라, 외부에서 전달한 값을 내부로 주입할 수도 있습니다. next(arg)를 호출할 때 전달된 인수는 이전 yield 표현식의 결과로 할당됩니다.

function* interactiveGen() {
  // 외부로 질문을 던집니다.
  const answer = yield "What is 5 + 5?";
  console.log("Received answer:", answer);
  // 다음 질문을 던집니다.
  const another = yield "What is 10 - 3?";
  console.log("Received answer:", another);
  return "End of questions";
}

const gen2 = interactiveGen();

const q1 = gen2.next().value; // "What is 5 + 5?"가 반환됩니다.
console.log("Question 1:", q1);

const q2 = gen2.next(10).value; // 외부에서 10을 전달
console.log("Question 2:", q2);

const result2 = gen2.next(7); // 외부에서 7을 전달; 제너레이터 종료
console.log(result2); // { value: "End of questions", done: true }

예외 처리: generator.throw

제너레이터 내부로 예외를 던지려면, 외부에서 generator.throw(error)를 호출할 수 있습니다. 이는 내부의 yield 표현식에서 예외를 발생시켜, try...catch로 처리할 수 있게 해 줍니다.

function* errorHandlingGen() {
  try {
    const question = yield "What is 2 + 2?";
    console.log("Question answered:", question);
  } catch (err) {
    console.error("Caught an error inside generator:", err.message);
  }
}

const gen3 = errorHandlingGen();
console.log(gen3.next().value); // "What is 2 + 2?" 출력

// 외부에서 예외를 던져 제너레이터 내부의 try...catch로 전달
gen3.throw(new Error("No answer found"));
// 콘솔에 "Caught an error inside generator: No answer found"가 출력됩니다.
  • gen3.throw(error)는 현재 중단된 yield 표현식에 예외를 던지며, 내부에서 해당 예외를 처리할 수 있도록 합니다.
  • 제너레이터의 예외 처리 로직을 외부에서 트리거 하고, 제너레이터의 상태를 안정적으로 관리할 수 있도록 지원합니다.