generator function을 통해 반환된 객체이다. Iterable protocol을 만족하면서 Iterator protocol을 만족하는 객체이다.
const generator = function*() {
yield 1;
}
console.dir(generator()); // generator
- Iterable protocol
Iterable protocol은Symbol.iterator
를 키로 갖는 인자가 없고iterator
객체를 반환하는 함수여야 한다.- Iterator protocol
아래의 사항들을 만족한다면 Iterator 객체이다.
-- 인자가 없는next()
메소드를 갖는다.
--next()
메소드는{value: any, done: boolean}
형식의IteratorResult
객체를 반환한다.
Generator.prototype.next()
: generator를 재개하는 메소드이다. next()함수의 인자로 제너레이터 내부에 yield
에게 값을 전달할 수 있다.const gen = function*() {
let i = 0;
while(true){
let v;
v = yield i++;
console.log(v)
}
}();
console.log(gen.next(4)); // {value: 0, done: false}
console.log(gen.next(5));
// 4,
// {value: 1, done: false}
Generator.prototype.return()
: yield
부분에 return
문이 들어간 것 처럼 작동한다. try...catch
문과 함께라면 제너레이터에게 제너레이터가 종료되었음을 알릴 수 있다.const gen = function*(){
yield 1;
try {
yield 2;
yield 3;
} finally {
yield 'clean up';
}
}
const gen1 = gen();
console.log(gen1.next()); // {value: 1, done: false}
console.log(gen1.return('early return')); // {value: 'early return', done: true');
console.log(gen1.next()); // {value: undefined, done: false}
const gen2 = gen();
console.log(gen2.next()); // {value: 1, done: false}
console.log(gen2.next()); // {value: 2, done: false}
console.log(gen2.return('early return')); // {value: 'clean up', done:false}
console.log(gen2.next()); // {value: 'early return', done:true}
console.log(gen2.next()); // {value: 'undefined', done:true}
try...catch
블록에서 return()
이 실행된다면 finally 문의 yield
가 실행되고 done
이 true가 된다.
Generator.prototype.throw()
: yield된 부분에서 throw
한 것처럼 작동하게 된다. try...catch
문과 같이 사용한다면 catch
문으로 바로 이동할 수 있다.function* gen4() {
while (true) {
try {
yield 42;
} catch (e) {
console.log('Error caught!');
}
}
}
const g = gen4();
g.next();
// { value: 42, done: false }
g.throw(new Error('Something went wrong'));
// "Error caught!"
// { value: 42, done: false }
const gen = function*() {
let i = 0;
while(true){
yield i++;
}
}();
console.log(typeof gen[Symbol.iterator])
console.log(gen[Symbol.iterator]) // 함수면서 Symbol.iterator를 가지고 있다. ==> iterable protocol을 만족한다.
console.log(typeof gen.next)
console.log(gen.next()) // 함수면서 next()함수를 가지고 있다. 또한, next()함수의 결과가 Iterator Result 객체이다. ==> iterator protocol이다.
console.log(gen[Symbol.iterator]() === gen) // true, Symbol.iterator 키가 자기 자신을 반환한다.
Symbol.iterator
메소드를 가지고 있고, iterator객체(자기자신)를 반환한다. => Iterable protocol을 만족한다.next()
메소드를 갖고 Iterator Result
객체를 반환한다. => Iterator protocol을 만족한다.두가지를 만족하는 generator는 Iterable이면서 Iterator 객체이다.
위 특성을 이용하여 직접 Generator처럼 동작하는 객체를 만들 수 있다.
const genObject = {
[Symbol.iterator]() {
return this;
},
next() {
const v = [1,2,3];
return {
value: v.shift(),
done: v.length === 0,
}
}
}
console.log(typeof genObject[Symbol.iterator]) // function
console.log(typeof genObject.next) // function
console.log(genObject.next()) // {value: 1, done: false}
console.log(genObject[Symbol.iterator]() === genObject) // true
또한, Iterable하기 때문에 spread연산자나 for...of
와 같은 구문을 사용할 수 있다.
const object = function* () {
let i = 10;
while (i--) {
yield i;
}
};
for (const iterator of object()) {
console.log(iterator);
} // 9,8,7,6,5,4,3,2,1,0
console.log(...object()); // 9 8 7 6 5 4 3 2 1 0
console.log(new Array(...object())); // [ 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 ]
suspend는 일시정지를 뜻하고 resume은 재개를 뜻한다. 일반적인 루틴의 경우 실행을 하면 루틴 내부 코드를 모두 실행하고 종료된다. 하지만 generator는 필요한 만큼만 실행할 수 있다.
const gen = function*() {
let i = 0;
while(true){
yield i++;
}
}();
console.log(gen.next().value) // 0
console.log(gen.next().value) // 1
console.log(gen.next().value) // 2
console.log(gen.next().value) // 3
console.log(gen.next().value) // 4
console.log(gen.next().value) // 5
generator gen
의 next()
를 호출하면 while문 안에 있는 yield
키워드에서 정지한다. i
를 1증가 시키고 빠져나온다. 다시 next()
를 호출하면 i
가 1인 상태로 다시 while문을 돌아 i
값을 리턴하고 1을 증가 시켜 2가 된다.
이렇게 루틴의 일시정지와 재개를 자유롭게 밖의 코드에서 제어할 수 있게 된다. 이런 성질을 이용하여 무한 수열값을 출력할 수 있게 된다.
이러한 특징은 저장할 수 없는 반복문이나 조건문과 같은 문(statement)을 값 형태로 저장했다고도 볼 수 있다.
const cardDeck = {
suits: ['♠️', '♣️', '♦️', '♥️'],
court: ['J', 'Q', 'K', 'A'],
[Symbol.iterator]: function* () {
for (const suit of this.suits) {
for (let i = 2; i <= 10; i++) {
yield suit + i;
}
for (const c of this.court) {
yield suit + c;
}
}
},
};
const arr = [...cardDeck];
console.log(arr, arr.length); // ["♠️2", "♠️3",...] 52
function* infinity() {
let i = 1;
while (true) {
yield i++;
}
}
function* take(n, iterable) {
for (const item of iterable) {
if (n <= 0) return;
n--;
yield item;
}
}
console.log([...take(5, infinity())]); // [1,2,3,4,5]
while
조건문을 true를 두어도 프로그램이 죽지 않는다.
function* bankAccount() {
let balance = 0;
while (balance >= 0) {
balance += yield balance;
}
return 'bankrupt';
}
let acct = bankAccount();
console.log(acct.next()); // { value: 0, done: false }
console.log(acct.next(50)); // { value: 50, done: false }
console.log(acct.next(-10)); // { value: 40, done: false }
console.log(acct.next(-60)); // { value: "bankrupt", done: false }
balance
상태를 generator가 종료할 때까지 유지하는 기능이 있다.
침고
- https://262.ecma-international.org/13.0/#sec-iteration
- https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterator_protocol
- https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Generator
- https://www.youtube.com/watch?v=gu3FfmgkwUc&t=819s&ab_channel=JSConf