제너레이터는 이터레이터이자 이터러블을 생성하는 함수다. function*으로 정의하고, yield로 값을 하나씩 반환한다.
function *gen() {
yield 1;
if (false) yield 2; // 실행되지 않음
yield 3;
}
let iter = gen();
iter.next(); // {value: 1, done: false}
iter.next(); // {value: 3, done: false}
iter.next(); // {value: undefined, done: true}
yield 2는 조건이 false라 건너뛴다. 제너레이터는 상태를 저장하기 때문에 다음 next 호출 시 멈춘 지점부터 재개된다.
제너레이터의 진짜 힘은 조합에서 나온다. infinity, limit, odds 세 제너레이터를 조합해서 홀수 순회를 만들 수 있다.
function *infinity(i = 0) {
while (true) yield i++;
}
function *limit(l, iter) {
for (const a of iter) {
yield a;
if (a === l) return;
}
}
function *odds(l) {
for (const a of limit(l, infinity(1))) {
if (a % 2) yield a;
}
}
for (const a of odds(10)) log(a); // 1 3 5 7 9
infinity는 무한히 값을 만들고, limit이 그 값을 잘라주고, odds가 홀수만 걸러낸다. 각 제너레이터는 필요할 때만 값을 꺼내기 때문에 무한 루프도 안전하게 쓸 수 있다.
제너레이터가 반환한 이터러블은 이터러블 프로토콜을 따르기 때문에 전개 연산자, 구조 분해도 그대로 쓸 수 있다.
log([...odds(10), ...odds(20)]);
const [head, ...tail] = odds(5);
log(head); // 1
log(tail); // [3, 5]
const [a, b, ...rest] = odds(10);
log(a); // 1
log(b); // 3
log(rest); // [5, 7, 9]