본문 바로가기
Language/JavaScript

제너레이터 - 이터러블/ 이터레이터

by 싯벨트 2025. 2. 20.
728x90

제너레이터(generator)란

ES6에서 도입된 제너레이터(generator)는 코드 블록의 실행을 일시 중지했다가 필요한 시점에 재개할 수 있는 특수한 함수입니다. 일반 함수와 다르게 호출자에게 함수 실행의 제어권을 양도하고, 호출자와 함수의 상태를 주고받을 수 있습니다. 제너레이터 함수를 호출하면 제너레이터 객체를 반환합니다. 이때 반환되는 제너레이터 객체는 이터러블(iterable)인 동시에 이터레이터(iterator)인 객체입니다.

정확한 이해를 위해 이터러블과 이터레이터란 무엇인지 살펴보겠습니다.

이터러블(iterable)

이터러블은 for...of 루프에서 순회할 수 있는 객체로, 내부에 Symbol.iterator 메서드를 가지고 있습니다. 그리고 이 메서드는 이터레이터를 반환합니다.

const iterableObj = {
  data: [1, 2, 3],
  [Symbol.iterator]() {
    let index = 0;
    return {
      next: () => {
        if (index < this.data.length) {
          return { value: this.data[index++], done: false };
        }
        return { value: undefined, done: true };
      }
    };
  }
};

for (const num of iterableObj) {
  console.log(num); // 1, 2, 3
}

이터레이터(iterator)

이터레이터는 next() 메서드를 가지고 있고, { value: any, done: boolean } 인 이터레이터 리절트 객체를 반환하는 객체입니다. 리턴값에서 value는 현재 값을 의미하고, done은 순회가 끝났는지 여부를 의미합니다. 이터레이터는 이터러블의 요소를 하나씩 순회하는 역할을 합니다.

const iterator = iterableObj[Symbol.iterator]();

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

 

제너레이터(generator)

제너레이터 함수는 function 키워드에 애스터리스크(*)가 붙은 function* 형태로 선언합니다. 그리고 하나 이상의 yield 표현식을 포함합니다. 제너레이터 함수를 선언한 뒤, 호출을 통해 제너레이터 객체를 생성하면, 이는 next를 포함하는 이터레이터이자, for...of 루프로 순회 가능한 이터러블임을 확인할 수 있습니다.

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

// 제네레이터 객체
const generator = gen(); 

// next 메서드 포함 => 이터레이터
console.log("next" in generator); // true
console.log(generator.next()); // { value: 1, done: false }
console.log(generator.next()); // { value: 2, done: false }
console.log(generator.next()); // { value: 3, done: false }
console.log(generator.next()); // { value: undefined, done: true }

// for...of 루프로 순회 가능 => 이터러블
console.log(Symbol.iterator in generator); // true
for (const value of gen()) {
    console.log(value); // 1, 2, 3
}

 

제너레이터의 일시 중지와 재개

제너레이터는 일반 함수와 달리 yield 키워드와 next 메서드를 통해 실행을 일시 중지했다가 필요한 시점에 다시 재개할 수 있습니다. 이때, 재개는 함수 호출자에게 제어권을 양도(yield)하여 필요한 시점에 함수 실행하는 식으로 이루어집니다. 다음 예제 코드의 흐름을 살펴보며 제너레이터의 동작을 살펴보겠습니다.

function* gen() {
 const x = yield 1;
 const y = yield (x + 10);
 return x + y;
}

const generator = gen(0);
let res = generator.next();
console.log(res); // {value: 1, done: false}
res = generator.next(10);
console.log(res); // {value: 20, done: false}
res = generator.next(20);
console.log(res); // {value: 30, done: true}

첫 번째 generator.next()

먼저, generator.next()가 호출되며 gen() 함수의 실행 컨텍스트가 콜 스택에 쌓입니다. 이때, generator 객체를 생성할 때 인수로 0을 넣었지만 처음 호출하는 next 메서드에는 인수가 전달되지 않습니다
함수 내부에서 yield 1을 만나며 실행이 일시 중지됩니다. 그리고 1이 value인 제러네이터 리절트 객체 { value: 1, done: false }가 출력됩니다. x는 아직 할당되지 않은 상태입니다.

두 번째, generator.next(10)

next(10)을 실행하면 이전 yield 문이 있었던 곳에서 실행이 재개됩니다. 인수로 전달된 10이 x에 할당되고, 두 번째 yield x + 10을 만나며 실행이 일시 중지됩니다. 이때 x에는 10이 할당되었으므로, 출력되는 리절트 객체는 { value: 20, done: false } 입니다.

세 번째, generator.next(20)

next(10)을 실행하면 이전 yield 문이 있었던 곳에서 실행이 재개됩니다. 인수로 전달된 20이 y에 할당되고, return x + y을 만나며 값을 반환하며 종료됩니다. 이때 x에는 10, y에는 20이 할당되었으므로, 출력되는 리절트 객체는 { value: 30, done: true } 입니다.

return을 만나며 함수가 종료되었으므로 gen()함수의 실행 컨텍스트는 콜 스택에서 제거되고 제너레이터의 상태도 종료됩니다.

 

참고자료

  • 모던 자바스크립트 Deep Dive, 46장. 제너레이터와 async/await