ES6
에서 추가된 함수이며, 기존의 함수 생성과는 다르게, function
키워드 뒤에 애스터리스크(Asterisk, *)를 붙여 생성하는 함수다.
제너레이터 함수를 실행하면 제너레이터 객체를 반환하며, 반환된 제너레이터 객체는 Well-formed iterable이다.
그럼 Well-formed iterable이란 무엇일까? 아래에서 자세히 알아보자!😆
Well-formed iterable은 iterator 객체이면서, 동시에 iterable 객체인 것을 말한다.
이 말은 iterable
객체가 반환하는 iterator
객체 또한, [Symbol.iterator]()
메서드를 구현한 iterable
객체라는 의미이다.
............ㅔ?
뭔가 말장난 같지만, 아래의 코드를 보면 이해할 수 있다!😃
const wfIterable = {
[Symbol.iterator]() {
let i = 0;
return {
next() {
return { value : i++, done : false};
},
[Symbol.iterator]() { return this; }
}
}
};
iterable
객체인 wfIterable
이 반환하는 iterator
객체를 보면 next()
메서드 뿐만 아니라, [Symbol.iterator]()
메서드도 구현되어 있으며 반환 값은 this
인 것을 볼 수 있다.
그럼 Well-formed iterable이 아닌 것과는 무슨 차이가 있을까? 바로, Well-formed iterable은 이전 진행 상황을 기억한다는 점이다.
둘의 차이를 간단한 예제를 통해 알아보자.
const nonwfIterable = {
[Symbol.iterator]() {
let i = 1;
return {
next() {
return { value : i++, done : false};
}
}
}
};
const iterator = nonwfIterable[Symbol.iterator]();
iterator.next(); // { value: 1, done: false }
iterator.next(); // { value: 2, done: false }
for (const value of nonwfIterable) {
if (value > 5) break;
console.log(value); // 1 2 3 4 5
}
1부터 증가하는 무한 이터레이터를 만들고, value
가 5를 넘게되면 반복문을 탈출하는 예제다.
for...of
문을 통해 순회한 결과 iterator.next()
를 두 번 호출했음에도, 1부터 5까지 출력하는 것을 볼 수 있다.
const wfIterable = {
[Symbol.iterator]() {
let i = 1;
return {
next() {
return { value : i++, done : false};
},
[Symbol.iterator]() {
return this;
}
}
}
};
const iterator = wfIterable[Symbol.iterator]();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
for (const value of iterator) {
if (value > 5) break;
console.log(value); // 3 4 5
}
결과를 보면 for...of
문으로 순회하기 전에 호출했던 iterator.next()
까지의 진행 상황을 기억하고, 그 이후부터 순회하는 것을 볼 수 있다.
제너레이터 함수를 호출하여 얻은 제너레이터 객체 또한 next()
메서드를 가지고 있다.
next()
메서드를 호출하면 제너레이터 함수가 실행되어 yield
문을 만날 때까지 진행하고, yield
에 명시되어있는 값을 반환한다.
const generator = function* () {
console.log('before yield 1');
yield 1;
console.log('before yield 2');
yield 2;
console.log('before yield 3');
yield 3;
}
const iterator = generator();
console.log(`yield 1 value : ${iterator.next().value}`);
console.log(`yield 2 value : ${iterator.next().value}`);
console.log(`yield 3 value : ${iterator.next().value}`);
// before yield 1
// yield 1 value : 1
// before yield 2
// yield 2 value : 2
// before yield 3
// yield 3 value : 3
특이한 점은, 아래처럼 next()
메서드에 인수를 전달해서 사용할 수도 있다는 점이다.
const generator = function* () {
console.log('Hello~');
let str = yield;
console.log(str);
console.log('nice to meet you');
str = yield;
console.log(str);
}
const iterator = generator();
iterator.next(); // Hello~
iterator.next('Hi!'); // Hi! nice to meet you
iterator.next('nice to meet you too!'); // nice to meet you too!
제너레이터는 next()
메서드에 인수를 전달해서 호출하는 경우, 진행을 멈췄던 yield
문을 next()
메서드에 전달한 인자값으로 치환해서 실행한다.
제너레이터는 yield*
표현식을 만나게 되면, 다른 제너레이터 함수에게 iterator
의 제어권을 위임한다.
function* gen1() {
yield 1;
yield* gen2();
yield 5;
}
function* gen2() {
yield 2;
yield 3;
yield 4;
}
const iterator = gen1();
for (const value of iterator) {
console.log(value); // 1 2 3 4 5
}
gen1()
의 메서드는 yield*
를 만나면 iterator
의 제어권을 gen2()
에게 위임하고, 이후 iterator
에서 호출하는 next()
메서드는 gen2()
함수를 실행하게 된다.
또한 yield*
는 구문이 아닌 표현이기 때문에, 아래처럼 값으로 평가된다.
function* gen1() {
result = yield* gen2();
}
function* gen2() {
return 'some value';
}
let result = 'default';
console.log(result); // default
const iterator = gen1();
iterator.next();
console.log(result); // some value
참고 자료
Generator | PoiemaWeb
function* | MDN
Generator | MDN
Iteration Protocols | MDN
Generator.prototype.next() | MDN
yield* | MDN