Search
Duplicate

Generator 오브젝트

1. Generator함수: function*, function* 선언문, function*표현식

function*

Generator function은 기존 함수 선언(표현)과 다르게 function뒤에 *키워드가 붙습니다. 그래서 function* 를 키워드로 사용합니다. 제너레이터 함수 형태는 일반 함수 형태처럼
function* 선언문, function* 표현식, GeneratorFunction이 있습니다.
//선언문 function* sports(one){} //표현식 const book = function*(one){}; //GeneratorFunction const music = Object.getPrototypeOf(function* (){}).constructor; const gen = new music();
JavaScript
복사
⇒ 여기서 function* 다음 소괄호를 이어서 작성해도 되고 하나 이상 띄워도 상관없습니다.

function* 선언문

일반 함수 선언문과 동일하게 function* 뒤에 함수명을 작성합니다. 제너레이터 함수를 호출하면 함수 블록({ }) 을 실행한 결과값이 아니라 Generator 오브젝트를 생성하여 반환합니다.
여기서 Generator 오브젝트는 iterator 오브젝트로 next()를 통해 결과값을 받을 수 있습니다.

코드 분석

function* sports(one, two){ yield one + two; } console.log(typeof sports);//function const obj = sports(1, 2); console.log(typeof obj);//object console.log(obj.next());//{value: 3, done: false}
JavaScript
복사
1.
console.log(typeof sports);
⇒ 제너레이터 함수의 타입은 function 입니다.
2.
const obj = sports(1, 2);
⇒ sports 함수를 호출하면 Generator Object를 생성하여 반환합니다. 이 반환하는 시점에서 함수의 블럭내의 코드를 싱행하지는 않습니다. 그리고 sports함수를 호출할 때 보낸 파라미터는 오브젝트에 설정되어 있습니다.
3.
new 연산자를 사용할 수 없는데 이는 단일 함수로 사용하겠다는 의미입니다.
4.
typeof obj
⇒ Generator 오브젝트의 타입은 object입니다.
5.
obj.next()
⇒ Generator 오브젝트가 iterator 오브젝트이기에 next()함수를 호출할 수 있고 이때 코드가 실행됩니다.

정리

제너레이터 함수를 호출하면 함수 블록 실행을 하지 않고 Generator 오브젝트를 생성및 반환.
Generator 오브젝트는 Iterator 오브젝트이다
new 연산자를 사용할 수 없다.
⇒ Generator 타입의 prototype에 메소드를 연결해 인스턴스를 만드는 것이 목적이 아니다.

function* 표현식

함수표현식과 표기법은 동일합니다. function* 다음에 함수 이름을 작성하는것은 필수가 아니며 만약 작성하게된다면 재귀적 호출(recursive call)에 사용할 수 있습니다. 하지만, 일반적으로는 함수 이름을 작성하지 않고 좌측에 설정되는 변수명이 함수 이름이 됩니다.
일반 함수 표현식과 마찮가지로 선언형태만 다를 뿐 다른것은 function* 선언문과 동일합니다.

코드 분석

const sports = function* (one){ yield one;}; const obj = sports(100: console.log(obj.next());//{value: 100, done:false}
JavaScript
복사
1.
const sports = function* (one){ yield one;};
⇒ 표현식 형태의 제너레이터 함수입니다.

2. GeneratorFunction

GeneratorFunction

GeneratorFunction.constructor를 사용해 제너레이터 함수를 생성합니다. 이 때 주의할점은 제너레이터 오브젝트가 아닌 함수를 생성한다는 점입니다. 이는 일반 함수를 만드는 Function 오브젝트와 유사한데, Function 오브젝트를 통해 함수를 만들때는 Function의 파라미터로 파라미터와 실행코드를 넘겨줘서 function을 만들어 줄 수 있습니다. 이처럼 GeneratorFunction.constructor를 사용할 때 마찮가지로 파라미터로 파라미터로 사용할 파라미터 이름과 함수 코드를 넘겨줘서 제너레이터 함수를 만들수 있습니다. 여기서 어째서 Generator가 아닌 GeneratorFunction의 constructor를 사용하는지에 대한 답변은 Generator에는 생성자가 없기 때문에 GeneratorFunction의 constructor를 사용하는 것이며 로직을 구현할때도 Generator의 constructor를 구하는 사전작업을 먼저 해줘야 합니다.
제너레이터 함수 구조
1.
제너레이터 함수 gen의 scope를 전개하면 prototype이 있습니다 . 하지만 이 prototype을 펼치면 constructor도 없고 next()나 throw()와같은 메소드 역시 연결되어 있지 않습니다.
2.
대신 __proto__가 있고 이 안에 constructor와 next, throw등이 있는데 __proto__안에 있다는 것은 다른 오브젝트의 prototype에 연결된 프로퍼티를 인스턴스 개념으로 생성하여 첨부했다고 할 수 있습니다.
3.
constructor를 살펴보면 해당 __proto__는 GeneratorFunction의 constructor가 첨부된 것이라는것을 확인할 수 있습니다.
const gen = function*(){}
JavaScript
복사

코드 분석

const fn = new Function("one", "return one"); console.log(fn(100));//100 const create = Object.getPrototypeOf(function*(){}).constructor; console.log(create); const sports = new create("one", "yield one"); const obj = sports(100); console.log(obj.next());// {value: 100, done: false}
JavaScript
복사
1.
create = Object.getPrototypeOf(function*(){}).constructor;
⇒ 제너레이터 함수를 생성하는 constructor(생성자)를 할당합니다. 이렇게 constructor가 할당되기에 이제 new 연산자를 통해 생성자 함수를 호출할 수 있습니다.
2.
log(create)
⇒ function GeneratorFunction(){[native code]}을 출력하는데 이는 create에 할당된 생성자가 Generator가 아닌 GeneratorFunction의 생성자이기 때문입니다.
3.
const sports = new create("one", "yield one");
⇒ GeneratorFunction을7 사용해 제너레이터 함수를 생성 후 sports변수에 할당합니다.
⇒ 파라미터로 sports에서 사용할 파라미터와 함수 코드를 작성합니다 .
one: 파라미터 이름
yield one: 함수 코드
4.
console.log(typeof sports);
⇒ new 연산자를 사용했지만 인스턴스가 아니기에 object가 아닌 function 입니다 function이라는 의미는 function* sports()로 제너레이터 함수를 선언한 것을 뜻하며 지금 까지 제너레이터 함수를 선언하는 처리를 했다는 말과 같습니다.
5.
const obj = sports(100);
⇒ sports에 작성된 함수코드를 호출하며 파라미터 이름인 one에 100을 작성합니다.
제너레이터 오브젝트를 생성및 반환하며 함수코드를 이 땐 실행하지 않습니다.
6.
console.log(obj.next());
⇒ 제너레이터 오브젝트는 이터레이터 오브젝트이기 때문에 next()메소드를 사용할 수 있고 이를 호출할 때 함수코드가 실행되어 평가결과가 반환됩니다.
GeneratorFunction Objest details

3. yield 키워드

[returnValue] = yield[표현식]
yield 키워드는 next()로 호출할 때마다 하나씩 실행되며 여러개의 yield가 있다면 next()를 호출할 때마다 차례대로 실행 후 반환됩니다. yield 키워드는 제너레이터 함수 실행을 멈추거나 다시 실행할때 사용합니다. 예를 들어 제너레이터 함수 내에 yield가 3개가 있따고 할때 next()를 호출하면 첫 yield 우측 표현식이 평가되어 결과가 반환되면 거기서 함수는 종료합니다. 그리고 다시 next()를 호출할 때 다음 yield 우측 표현식이 평가되어 반환되며 진행되죠. 여기서 표현식을 작성하지 않을 경우에는 undefined가 반환됩니다.
[returnValue]는 오른쪽의 평가 결과가 설정되지 않고 다음 next()에서 파라미터로 넘겨준 값이 설정됩니다. const returnValue = yield 10;이라면 10이 설정되는 것이아닌 다음 next()호출시 넘겨주는 파라미터 값이 returnValue에 설정된다는 의미입니다.

코드 분석

function* sports(one){ yield one + 10; yield; const value = yield one + 50; }; const obj = sports(30); console.log(obj.next()); console.log(obj.next()); console.log(obj.next()); console.log(obj.next(200));
JavaScript
복사
[실행 결과]
{value: 40, done: false} {value: undefined, done: false} {value: 80, done: false} {value: undefined, done: true}
1.
console.log(obj.next(200));
⇒ 제너레이터 함수 sports는 3개의 yield가 있습니다. 그렇기에 3번의 next()호출까지는 그때그때 맞는 yield 우측의 표현식의 평가결과를 반환합니다.
네 번째 obj.next(200)을 호출 할 때는 더 이상의 yield가 존재하지 않아 undefined가 호출되는 것이고 이 때 넘겨준 200이라는 값은 코드내 const value가 [returnValue]이기에 200이 설정되지만 yield가 없어 undefined가 출력됩니다.
yield의 표현식을 평가하면 호출한 곳으로 {value: 값, done: true/false}를 반환합니다.
1.
obj.next()
⇒ yield one; 실행되어 {value:10, done:false}반환
2.
obj.next() 두번 째 호출
⇒ check = 20을 실행하지만 yield가 아니기에 {value: undefined, done:true} 반환.
functon* sports(one){ yield one; const check = 20; }; const obj = sports(10); console.log(obj.next()); console.log(obj.next());
JavaScript
복사
여기서 yield의 반환 형태는 {value, done}인데
value는 표현식의 평가 결과를 설정하며 yield를 설정하지 못하면 undefined를 설정합니다.
done은 yield의 실행여부를 설정하는데 실행할 경우 false 실행하지 못했을경우 true를 설정
function* sports(one){ let two = yield one; let param = yield one + two; yield param + one; }; const obj = sports(10); console.log(obj.next()); console.log(obj.next()); console.log(obj.next(20)); console.log(obj.next());
JavaScript
복사
[실행 결과]
{value: 10, done: false} {value: NaN, done: false} {value: 30, done: false} {value: undefined, done: true}
1.
const obj = sports(10);
⇒sports 제너레이터 오브젝트를 생성하며 파라미터 값으로 10을 작성해 one에 설정됩니다.
2.
첫 번째 console.log(obj.next());
⇒let two = yield one; 이 실행되며 one에 설정된 값인 10이 반환됩니다. 이 때 two는 [returnValue]이기 때문에 10을 할당하지 않습니다. 다음 next()에서 전달되는 파라미터가 설정됩니다.
3.
두 번째 console.log(obj.next());
⇒ 두 번째 next()호출시 파라미터를 작성하지 않았기 때문에 [returnValue]인 two는 undefined입니다. 두 번째 yield의 우측 표현식 one + two는 10 + undefined이기 때문에 NaN 이 반환됩니다.
4.
세 번째 console.log(obj.next(20));
⇒여기서 작성한 파라미터 값은 다음 [returnValue]인 param에 설정됩니다. 세 번째 yield의 우측 표현식은 param+one으로 20 + 10이 되어 30이 반환됩니다.
5.
네 번째 console.log(obj.next());
⇒ 네 번째 이후 next()호출은 더 이상 실행할 yield가 없기에 처리하지 않고 value는 undefined이고 done은 true를 반환합니다.

4. next()

next()

GeneratorObject에서 next()를 호출하면 yield단위로 실행됩니다. next()의 호출은 yield 수만큼 할 수가 있는데 yield 수만큼 호출이 끝나면 그 뒤는 next()를 호출해도 undefiled가 출력됩니다.
next()를 호출하면 이전 yield의 다음부터 다음 yield까지 실행하는데, 역시 여기서도 다음 yield가 없을경우 {value: undefined, done: true}를 반환합니다. 만일 제너레이터 오브젝트 내에 yield가 없다면 코드 실행은 하지만 yield가 없기 때문에 반환되는 값이 없습니다. 일반 함수처럼 return을 작성해줬을 경우에는 값을 반환해주지만, done이 true로 설정되어 로직이 종료됩니다.

코드 분석

//yield가 없는 경우 function* noYieldGen(value){ console.log(++value); const savedValud = value; } //return 문을 작성했을 때 function * useReturn(value){ return ++value; } const obj1 = noYieldGen(200); const obj2 = useReturn(200); console.log(obj1.next()); console.log(obj2.next());
JavaScript
복사
[실행 결과]
201 {value: undefined, done: true} {value: 201, done: true}
1.
console.log(obj1.next());
⇒ 첫 번째 obj1.next()를 호출하면 제너레이터 함수를 실행하여 증가한 value값을 출력하지만 yield가 없기 때문에 값이 반환되지 않습니다.
2.
console.log(obj2.next());
⇒ obj2.next()를 호출하면 제너레이터 함수를 실행하여 증가된 value값을 반환합니다.
하지만 return으로 값을 반환하면 {done: true}이며 더 이상 호출해도 반환값이 없습니다.
next()를 호출 하면서 살펴보면 제너레이터 함수에는 또 하나의 특징을 발견할 수 있습니다.
이는 함수를 호출 하면서 전달했던 파라미터 값들이 초기화 되는게 아닌 설정한 값 그대로 남아있다는 것인데요. 제너레이터 함수에 파라미터로 100을 작성하고 next()를 호출하며 [returnValue]에 값을 세팅하는 것들과 평가결과 등이 모두 저장되어 다음 next()를 호출할 때 yield 로 표현식이 평가될 때 사용할 수 있습니다. 마치 비디오 테이프의 pause/play와같습니다. 이전 값들이 초기화되지 않죠.
일반 함수가 로직을 완전히 수행 후 종료되어 다시 호출하면 다시 처음부터 시작한다면, 제너레이터 함수는 1회성이며 play&pause 식으로 next()를 호출 할 때마다 진행됩니다.

5. yield 반복, 다수의 yield처리

지금까지는 매번 각각의 yield를 next()로 호출하고 yield를 선언한 만큼 호출이 끝나면 undefined가 되었습니다. 하지만 이런 제한적인 상황이 아니라 동적으로 yield를 사용하거나 반복적으로 사용하고싶다면 어떻게 해야할까요. 반복문 문법(while, for...)들을 이용해 yield를 반복해봅시다.

코드 분석

lety status = true; function* sports(){ let count = 0; while(status){ yield ++count; } } const obj = sports(); console.log(obj.next()); console.log(obj.next()); status = false; console.log(obj.next());
JavaScript
복사
1.
console.log(obj.next());
⇒ obj.next()를 호출하여 제너레이터 함수sports의 코드를 실행합니다.
2.
while(status){ yield ++count;}
⇒ status가 true이므로 yield를 수행하며 yield 우측 표현식 ++count의 평가결과 1이 반환됩니다
3.
status = false;
⇒ 전역 변수 status의 값을 true에서 false로 설정합니다.
4.
console.log(obj.next())
⇒ status가 false가 되었으므로 반복문을 수행하지 않습니다. {value: undefined, done: true}을 반환하고 done이 true이므로 이터레이터를 더 이상 사용할 수 없습니다.

다수의 yield 처리

한 줄에 다수의 yield와 return을 작성하여 처리할수도 있습니다.
function* sports(){ return yield yield yield; }; const obj = sports(); console.log(obj.next()); console.log(obj.next(10)); console.log(obj.next(20)); console.log(obj.next(30));
JavaScript
복사
1.
첫 번째 next()호출(console.log(obj.next()))
⇒ 첫 번째 yield를 수행합니다. 이때 yield의 반환 값이 없기에 undefined를 반환합니다.
2.
두 번째 next()호출(console.log(obj.next(10)))
⇒ 두 번째 next()를 호출하며 파라미터로 10을 작성했습니다. sports() 제너레이터 함수내부의 다음yield를 수행합니다. 여기서 파라미터로 작성한 값을 받을 변수가 없다면 파라미터로 념겨준 값을 반환합니다. 그렇기에 10을 반환받습니다 {value: 10, done: false}를 반환합니다.
3.
세 번째 next()호출)(console.log(obj.next(20))
⇒ 두 번째와 마찮가지로 동작하며 파라미터로 넘겨준 값 20을 반환합니다.
4.
네 번째next()호출(console.log(obj.next(30))
⇒ 네 번째 next()를 호출할 때는 제너레이터 함수에 처리할 yield가 없습니다. 그렇기에 return문법에 따라 파라미터로 작성한 값 30을 반환하며 done: true를 반환합니다. 만약 여기서 return문을 작성하지 않으면 {value: undefined, done:true}로 value로 30이 아닌 undefined를 반환합니다.

6. yield 분할 할당, for-of 반복

yield 분할 할당

yield를 대괄호 안에 여러개 작성할수도 있습니다. return [yield yield]
function* sports(){ return [yield yield]; }; const obj = sports(); console.log(obj.next()); console.log(obj.next(10)); const last = obj.next(20); console.log(last); console.log(last.value);
JavaScript
복사
1.
첫 번째 obj.next(), 두 번째 obj.next(10)
⇒ 첫 번째와 두 번째 next()호출은 기존의 yield 호출과 동일합니다. 표현식이 없기에 전달받은 파라미터를 출력해주기에 각각 {value:undefined, done:false}, {value:10, done:false}를 반환합니다.
2.
세 번째 obj.next(20)
⇒ 두 번째까지의 next()호출만 봐서는 기존의 yield처리와 동일합니다. 그런데 이 최종 yield처리 후 return에 의해 반환될 때 전달받은 파라미터는 return 우측의 표현식인 [ ]에 설정되어 20이 아닌 [20]으로 설정되어 반환됩니다. {value:[20], done:true}

for-of문으로 반복

제너레이터 함수를 매번 next()를 호출하여 반환 오브젝트{value, done}에서 꺼내어 쓰고, 또 done을 검사해 추가 반복 가능성 여부를 검사하는것은 불편합니다. 여기서 이 제너레이터 함수가 이터레이터 오브젝트라는 점에 착안을 하면 for-of사용이 가능하다는 것도 상기시킬 수 있습니다.
function* sports(count){ while(true){ yield ++count; }; }; for (let point of sports(10)){ console.log(point); if(point < 12){ break; }; }
JavaScript
복사
[실행 결과]
11
12
13
1.
for (let point of sports(10)){...}
⇒ 처음 for-of문을 시작하면 sports(10)으로 제너레이터 오브젝트를 생성하고, 작성한 파라미터 10이 count에 설정됩니다. 이 때 생성한 제너레이터 오브젝트를 저장할 변수가 없으며 엔진 내부에 저장합니다.
const engine = sports(10);과 같으며 engine이 엔진 내부의 이름으로 가정합니다.
2.
다시 sports*()를 호출하고, 이는 next()와 같지만 반환 값이 다릅니다. 기존 next()가 value와 done으를 오브젝트 타입으로 반환한다면 for-of에서는 value만 꺼내 point에 설정합니다.
3.
while(true)이기에 done:true를 이용해 종료할 수 없기 때문에 break;문으로 종료시켜야 합니다.

7. 제너레이터 오브젝트 메소드: return(), throw()

return()

이터레이터를 종료시키는 메소드입니다. 파라미터로 작성한 값을 반환값으로 가져옵니다. 기존의 next()play/pause와 비슷하다면 return()stop이라 볼 수 있습니다.
function* sports(count){ while(true){ yield ++count; }; }; const obj = sports(10); console.log(obj.next()); console.log(obj.return(70)); console.log(obj.next(50));
JavaScript
복사
[실행 결과]
{value: 11, done: false} {value: 70, done: true} {value: undefined, done: true}
1.
obj.return(70)
⇒ 이터레이터를 종료시키며 파라미터 값 70을 반환합니다. 이때 done:true가 됩니다. 이는 이터레이터가 종료되어 진행이 불가능해진다는 의미입니다.
2.
obj.next(50)
⇒ 이터레이터가 종료되었으므로 {value: undefined, done:true}가 반환됩니다. 파라미터 50이 반환되지 않습니다.

throw()

의도적으로 Error를 발생시켜 예외상황을 유도할 때 사용하는 메소드입니다. 제너레이터 함수의 catch()문에서 에러를 받습니다.
function* sports(){ try{ yield 10; } catch(message){ yield message; }; yield 20; }; const obj = sports(); console.log(obj.next()); console.log(obj.throw("에러 발생")); console.log(obj.next());
JavaScript
복사
1.
obj.throw("에러 발생")
⇒sports()내부의 catch()의 yield message;를 수행하며 에러를 발생합니다.
⇒ {value: "에러 발생", done: false}를 반환합니다. 이때 제너레이터가 종료되지 않습니다.
2.
obj.next();
⇒ throw()로 인해 에러가 발생했지만, 제너레이터가 종료된 것이 아니기에 다음 yield를 수행합니다. {value: 20, done: false}
반면 throw문을 외부가아닌 제너레이터 함수에 작성하면 어떻게될까?
function* sports(){ throw "에러 발생"; yield 10; } const obj = sports(); try{ const result = obj.next(); } catch(message){ console.log(message);//에러 발생 } console.log(obj.next());//{value: undefined, done: true}
JavaScript
복사
⇒ obj.next()를 실행하면 제너레이터 안에서 throw를 만나며 에러가 발생됩니다. 이렇게 제너레이터 함수에서 에러가 발생하면 이터레이터는 종료됩니다. 그래서 마지막 줄에서 next()를 호출해도 {value: undefined, done:true}를 반환할 뿐 yield 10;은 실행되지 않습니다.

8. yield* 표현식

Syntax: yield* expression
기존의 yield를 일반적으로 사용할때 yield우측의 표현식의 평가결과를 반환합니다.
이런 yield에 *키워드를 붙히면 우측의 표현식에 따라 다른 처리로직을 따르게 되는데요. 기본 골조는 play/pause 를 유지하지만, 그 처리되는 방식이 달라집니다.

1. yield*의 표현식이 배열인 경우

function* sports(){ yield* [10, 20]; }; const obj = sports(); console.log(obj.next()); console.log(obj.next());
JavaScript
복사
[실행 결과]
{value: 10, done: false} {value: 20, done: false}
⇒ 첫 obj.next()호출시에는 yield* [10, 20]에서 첫 번째 요소인 10을 반환합니다. 두 번째 obj.next()호출시에는 다음 요소를 반환합니다. 이처럼 yield*의 표현식이 배열인 경우 next()를 호출할 때마다 배열의 엘리먼트를 순서대로 반환한 뒤 다음 yield를 찾습니다.

2. yield*의 표현식이 제너레이터 함수인 경우

function* point(count){ yield count + 5; yield count + 10; }; function * sports(value){ yield* point(value); yield value + 20; }; const obj = sports(10); console.log(obj.next()); console.log(obj.next()); console.log(obj.next());
JavaScript
복사
[실행 결과]
{value: 15, done: false} {value: 20, done: false} {value: 30, done: false}
1.
첫 번째 obj.next()를 호출하면 제너레이터 함수 sports 의 첫 번쨰 yield* point(value)를 실행합니다. 여기서 point()는 제너레이터 함수이기에 우선 제너레이터 오브젝트를 생성합니다. 기존 원리대로라면 여기서 next()를 호출해야 yield가 수행되지만 자동으로 point()의 첫 번째 yield count + 5 가 수행되어 {value: 15, done: false}를 반환하여 반환받은 sports() 에서 반환값을 받아 반환합니다.
2.
다음 두 번째 obj.next()호출시 마지막으로 호출된 point()함수위치에서 시작해 다음 yield인 yield count + 10 를 실행해 반환하며 sports()에서 반환받은 값을 반환합니다.
3.
세 번째 obj.next()호출 시 현재 수행되던 제너레이터 함수 point()의 yield를 모두 처리했기에 다음 yield value + 20을 실행하여 반환합니다.

3. yield*의 표현식이 재귀함수인 경우

function* sports(point){ yield point; yield* sports(point + 10); }; const obj = sports(10); console.log(obj.next()); console.log(obj.next()); console.log(obj.next());
JavaScript
복사
[실행 결과]
{value: 10, done: false} {value: 20, done: false} {value: 30, done: false}
1.
첫 번째 obj.next() 호출 시 yield point; 실행하여 반환 - {value: 10, done: false}
2.
두 번째 obj.next()를 호출시 yield* sports(point + 10);으로 자기자신을 호출하며 첫 번째 라인의 yield point;를 실행하여 {value: 20, done: false} 이 반환됩니다.
3.
세 번째 obj.next() 호출시 2번째와 마찮가지로 yield* sports(point + 10)를 호출하며 첫 번째 줄의 yield point를 실행합니다.