이터레이터(Iterator)와 제네레이터(Generator)
for ... of 라는 것을 공부하다 보면 Iterable한 객체들에서만 사용할 수 있다고 한다. 다른 언어에서 흔히 사용하는 순회할 수 있는 객체(Iterable한 객체)를 JS는 왜이리 어렵게 설명해놨을까? 그냥 단순 반복인 줄 알았는데 프로토콜로 돌아간다는 사실을 알게 되었고, 이 개념을 알기 위해서 공부한 것을 정리하려고 한다.
📌 용어 정리
1. Iterable 하다
JS에서 Iterable한 객체는 [Symbol.iterator]() 메서드를 가지고 있는 객체를 말한다. 이 메서드는 반드시 이터레이터 객체(iterator)을 반환해야 한다.
2. Iterator(이터레이터)
이터레이터는 next() 메서드를 직접 소유하거나 상속받은 객체로, { value, done } 프로퍼티를 가진 객체를 반환한다.(이때, next()가 반환하는 객체는 일반 객체)
Iterable 하면 반드시 Iterator인가? Iterator는 무조건 Iterable한 객체인가?
정답은 "아니오". Iterable한 객체는 Iterator를 반환하는 메서드를 가진거지 반드시 Iterator가 아니다. 예를 들어,
Map객체는 Iterable하지만, Iterator는 아니다.Iterator는 Iterable하지 않은 객체일 수 있다.
[Symbol.iterator]()메서드가 없을 수도 있기 때문.
3. Generator(제네레이터)
Generator는 특별한 종류의 함수로, 이터레이터를 생성하는 편리한 문법을 제공한다.
Generator 함수는 function* 문법을 사용하며, yield 키워드를 통해 실행을 중간에 멈췄다가(next 호출 시) 이어서 실행할 수 있다.
특이한 점은, Generator 함수는 호출되어도 즉시 실행되지 않고 next()로 진행되는 이터레이터 객체를 반환한다는 것이다.
📌 for...of 이해하기
for...of 문은 Iterable한 객체의 요소들을 순회할 때 사용된다. 이때 3가지 과정으로 동작한다.
- 객체의
[Symbol.iterator]()메서드를 호출하여 이터레이터를 얻는다. - 이터레이터의
next()메서드를 호출하여 값을 가져온다. done프로퍼티가true가 될 때까지 위 과정을 반복한다.
const iterable = [1, 2, 3];
const iterator = iterable[Symbol.iterator]();
while (true) {
const { value, done } = iterator.next();
if (done) break;
/*
이 부분이 for...of의 반복 본문에 해당함
*/
}
이런 동작을 하기 때문에 for...of 문이 Iterable한 객체에서만 사용 가능한 것이다.
📌 Generator 알아보기
이런 이터레이터(iterator)를 직접 구현하기 어려울 때 사용하는 문법이 Generator이다.
1. yield
실행을 일시정지시키고 값을 내보낸다. 다음 next() 호출 시 멈췄던 지점부터 다시 실행된다.
function* simpleGenerator() {
console.log('시작');
yield 1;
console.log('중간');
yield 2;
console.log('끝');
return 3;
}
const gen = simpleGenerator();
console.log(gen.next()); // "시작" 출력 후 { value: 1, done: false }
console.log(gen.next()); // "중간" 출력 후 { value: 2, done: false }
console.log(gen.next()); // "끝" 출력 후 { value: 3, done: true }
2. yield*
yield* iterable은 다른 Iterable 한 객체에게 순회를 위임한다. 말이 좀 어려우니 아래 예시를 보자.
function* numbers() {
yield 1;
yield* [2, 3]; // 여기서 [2,3]을 내가 직접 하나씩 yield 하는 것과 같음
yield 4;
}
console.log([...numbers()]); // [1, 2, 3, 4]
// 아래와 같이 동작
function* numbers2() {
yield 1;
yield 2;
yield 3;
yield 4;
}
console.log([...numbers2()]); // [1, 2, 3, 4]
3. return
Generator 함수를 종료하고 { value: 반환값, done: true }를 만든다.
function* generatorWithReturn() {
yield 1;
yield 2;
return 'finished';
yield 3; // 실행되지 않음
}
const gen = generatorWithReturn();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 'finished', done: true }
4. throw
iterator.throw(err)로 제네레이터 내부의 멈춰 있던 지점(yield)에 예외를 주입할 수 있다. 내부에 try/catch가 없으면 예외가 호출자에게 전파된다.
function* errorGenerator() {
try {
yield 1;
yield 2;
} catch (e) {
console.log('에러 캐치:', e);
}
yield 3;
}
const gen = errorGenerator();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.throw('에러 발생!')); // "에러 캐치: 에러 발생!" 출력 후 { value: 3, done: false }

