제너레이터(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(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
'Language > JavaScript' 카테고리의 다른 글
프로토타입 (0) | 2025.03.08 |
---|---|
async/await - 실행 컨텍스트로 이해하기 (0) | 2025.02.20 |
Promise 개념 살펴보기(비동기, 힙메모리, 마이크로태스크 큐) (0) | 2025.02.12 |
실행 컨텍스트 뿌시기 part2 (0) | 2023.08.27 |
실행컨텍스트 뿌시기. part1 (0) | 2023.08.25 |