- Published on
Generator로 Iterator 더 쉽게 만들기
- Authors

- Name
- Nostrss
- Github
- Github

들어가며
어제 글에서 커스텀 Iterator를 만들어봤다. Symbol.iterator 메서드와 next() 메서드를 직접 구현해야 했는데, 솔직히 좀 번거로웠다. 다행히 JavaScript에는 Generator라는 기능이 있어서, Iterator를 훨씬 쉽게 만들 수 있다.
Generator 기본 문법
Generator는 function* 키워드로 정의한다. 별표(*)가 핵심이다.
function* myGenerator() {
yield 1
yield 2
yield 3
}
yield 키워드는 값을 하나씩 "양보"한다고 생각하면 된다. Generator 함수를 호출하면 바로 실행되지 않고, Iterator 객체를 반환한다.
const gen = myGenerator()
console.log(gen.next()) // { value: 1, done: false }
console.log(gen.next()) // { value: 2, done: false }
console.log(gen.next()) // { value: 3, done: false }
console.log(gen.next()) // { value: undefined, done: true }
next()를 호출할 때마다 다음 yield까지 실행되고 멈춘다. 마지막 yield 이후에는 done: true가 반환된다.
Counter를 Generator로 다시 만들기
어제 만들었던 createCounter를 다시 보자.
// 어제의 방식
function createCounter(start, end) {
return {
[Symbol.iterator]() {
return this
},
next() {
if (start > end) return { done: true }
return { value: start++, done: false }
},
}
}
Generator로 다시 만들면:
// Generator 방식
function* createCounter(start, end) {
while (start <= end) {
yield start++
}
}
코드가 확 줄었다. Symbol.iterator도, next()도, { value, done } 객체도 직접 만들 필요가 없다. Generator가 알아서 처리해준다.
for (const num of createCounter(1, 5)) {
console.log(num) // 1, 2, 3, 4, 5
}
console.log([...createCounter(1, 3)]) // [1, 2, 3]
피보나치 수열을 Generator로
피보나치 수열도 마찬가지다.
// 어제의 방식
function createFibonacci(limit) {
let pre = 0,
cur = 1
return {
[Symbol.iterator]() {
return this
},
next() {
if (pre + cur > limit) return { done: true }
const value = cur
;[pre, cur] = [cur, pre + cur]
return { value, done: false }
},
}
}
Generator로 다시 만들면:
// Generator 방식
function* createFibonacci(limit) {
let pre = 0,
cur = 1
while (cur <= limit) {
yield cur
;[pre, cur] = [cur, pre + cur]
}
}
로직에만 집중할 수 있어서 훨씬 읽기 쉽다.
console.log([...createFibonacci(100)])
// [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
Generator의 특징
1. Iterable이면서 Iterator
Generator가 반환하는 객체는 Iterable이면서 동시에 Iterator다.
function* gen() {
yield 1
}
const g = gen()
// Iterator 확인
console.log(typeof g.next) // 'function'
// Iterable 확인
console.log(typeof g[Symbol.iterator]) // 'function'
console.log(g[Symbol.iterator]() === g) // true
g[Symbol.iterator]()가 자기 자신을 반환한다. 이게 바로 어제 수동으로 구현했던 패턴이다.
2. 지연 평가 (Lazy Evaluation)
Generator는 값이 필요할 때만 계산한다. 무한 수열도 만들 수 있다.
function* infiniteCounter() {
let n = 0
while (true) {
yield n++
}
}
const counter = infiniteCounter()
console.log(counter.next().value) // 0
console.log(counter.next().value) // 1
console.log(counter.next().value) // 2
// 필요한 만큼만 가져올 수 있다
당연히 spread operator로 펼치면 무한 루프에 빠지니까 조심해야 한다.
// 이렇게 하면 무한 루프에 빠진다!
// const all = [...infiniteCounter()] // 절대 끝나지 않음 - 실행 금지!
// spread operator는 done: true가 될 때까지 계속 next()를 호출하기 때문이다.
// infiniteCounter()는 while(true)로 영원히 값을 생성하므로 끝이 없다.
3. 상태 유지
Generator는 실행 컨텍스트를 유지한다. yield에서 멈췄다가 다음 next() 호출 시 이어서 실행된다.
function* stateful() {
console.log('시작')
yield 1
console.log('중간')
yield 2
console.log('끝')
}
const s = stateful()
s.next() // '시작' 출력, { value: 1, done: false }
s.next() // '중간' 출력, { value: 2, done: false }
s.next() // '끝' 출력, { value: undefined, done: true }
정리
- Generator 함수는
function*로 정의하고,yield로 값을 반환한다 - Generator를 사용하면 Iterator를 훨씬 간결하게 만들 수 있다
- Generator 객체는 Iterable이면서 Iterator다
- 지연 평가 덕분에 무한 수열도 표현할 수 있다
커스텀 Iterator가 필요하다면, 대부분의 경우 Generator를 쓰는 게 더 낫다.
참고 자료
출처
인프런 함수형 프로그래밍과 JavaScript ES6+ 강의를 학습하고 정리한 내용입니다.

