ECMAScript 2015
by jbee
[ES6] 10. Generator
Generator는무엇인가?
 Generator function 으로반환된값을 Generator Object 라고 하고
이 Generator Obejct 는 iterator 프로토콜을따르고 있다. 즉
 [Symbol.iterator] 가 프로퍼티에추가되어있다는것이다.  Generator
function 안에서는 yield 라는키워드를사용해서함수내부에작성된코
드를전부실행하지않는다. 제너레이터함수는 yield 를기준으로실행을나
누어서진행한다.  iterator 프로토콜을따르고 있기 때문에순차적으로실
행할수있는것이다.
cf> Generator function 를  제너레이터 함수 로,  Generator
Object 를  제너레이터 오브젝트 로 표기.
Generator Function
 function* 로표현할수있으며, 작성할때는일반 function 처럼선언문
과 표현식으로작성할수있다.
function* calc(prev, post) {
console.log("generator start");
yield prev + post;
}
or
let calc = function*(prev, post) {
console.log("generator start");
yield prev + post;
}
제너리에터함수에의해반환되는값은제너레이터오브젝트이다.
let generator = calc(1, 2);
console.log(typeof generator); // object
제너레이터오브젝트를반환하는순간에는오브젝트를반환하기만할뿐, 내부
코드는실행되지않는다.
Generator Object
 new 키워드를사용하여인스턴스를생성할수없다.
let cal = new calc();// Error
//TypeError: calc is not a constructor
next
 next() 메소드를통해제너레이터함수를실행시킬수있다.
console.log(generator.next());
//generator start
//{ value: 3, done: false }
console.log(generator.next());
//{ value: undefined, done: true }
value, done
 iterator 의 next() 메소드를실행시킨것처럼 value 와 done 이라는
프로퍼티를갖고 있는객체로반환된다. 그런데첫번째 next() 메소드실행
시에는 generator start 가 출력되었는데, 두번째실행시에는출력되지
않았다.  yield 라는키워드를중심으로함수가 나눠실행되는것이다.
yield 키워드, 함수를실행하고 멈출수있다.
[returnValue] = yield[expression]
위와같은구문으로 yield 를작성할수있다.
위의예제코드에서살펴봤듯이,  next() 메소드의반환값은 value 와
 done 으로구성되어있는오브젝트이다. 제너레이터의메소드 next() 에서
이두가지의값은yield에의해결정된다.  value 가 결정되는규칙이조금 복
잡하다.
value 결정규칙
 expression 으로반환되는값이할당.
이때,  expression 에있는값이 returnValue 에할당되지않는다.
 expression 에아무것도없으면 undefined 가 할당.
이때,  next() 의파라미터로넘겨지는값이 returnValue 에할당된
다.
done 결정규칙
계속수행할 yield 가 남아있으면 false .
더이상실행할 yield 가 없으면 true .
예제 코드를 살펴보자.
function* calc(prev, post) {
let result = 0;
console.log(`Initial result: ${result}`);
result = yield prev + post;
console.log(`Middle result: ${result}`);
result = yield;
console.log(`Last result: ${result}`);
}
let generator = calc(10, 20);
위에서언급한규칙에대한내용을모두담고 있는예제코드이다.
 console.log() 에는어떠한값이찍히게 될까? 코드를통해하나씩살펴보
자.
console.log(generator.next());
// Initial result: 0
// { value: 30, done: false }
 next() 메소드를실행시키면첫번째yield까지실행한다.
초기  result 변수에대한값이출력되고,
 expression 으로계산된값인 30 이 value 이출력된다.
아직 yield 가 남았으니 done 은 false 가 되겠다.
console.log(generator.next());
// Middle result: undefined
// { value: undefined, done: false }
두번째yield까지실행한다.
 expression 값이 result 에할당되지않은것을확인할수있다.
아직 yield 가 남았으니 done 은 false 가 되겠다.
console.log(generator.next(20));
// Last result: 20
// { value: undefined, done: true }
남은yield가 없으므로brace까지실행한다.
 next() 메소드의파라미터로넘겨진 20 이 result 변수에할당된것을
확인할수있다.
더이상 yield 키워드가 없으므로 done 은 true 가 된다.
yield 대신return
function* calc(prev, post) {
return prev + post;
}
let generator = calc(10, 20);
console.log(generator.next());
// { value: 30, done: true }
 return 키워드뒤에오는값이 value 에할당되고  yield 키워드의유무
와상관없이 done 에는 true 가 할당된다.  return 은수행되고 있는이터
레이터를종료시키는역할을수행한다.
이터레이터종료하기
 yield 키워드의유무와상관없이이터레이터를종료하고자할때는제너레
이터오브젝트의 throw() 메소드와 return() 메소드를사용할수있다.
function* idMaker(prev, post) {
let value = 0;
while(true) {
yield ++value;
}
}
let g = idMaker();
console.log(g.next());// { value: 1, done: false }
console.log(g.next());// { value: 2, done: false }
console.log(g.next());// { value: 3, done: false }
console.log(g.return(100));// { value: 100, done: true }
 return() 메소드의파라미터로넘어가는값이 value 에할당된다.
위의예제코드에 return() 대신 throw() 를호출하게 되면파라미터로
넘겨준Error Message를출력하고 이터레이터가 바로종료된다.
function* idMaker(prev, post) {
let value = 0;
try {
while(true) {
yield ++value;
}
} catch(e) {
console.log(`Error message: ${e}`);
}
}
let g = idMaker();
console.log(g.next());// { value: 1, done: false }
console.log(g.next());// { value: 2, done: false }
console.log(g.next());// { value: 3, done: false }
console.log(g.throw("Throw Exception"));
//Error message: Throw Exception
//{ value: undefined, done: true }
여기서 try-catch 구문에 yield 를추가하면어떻게 될까?
바로이터레이터가 종료되지않고  yield 다음의구문이실행된다.
function* idMaker(prev, post) {
let value = 0;
try {
while(true) {
yield ++value;
}
} catch(e) {
yield e;
}
}
let g = idMaker();
console.log(g.next());// { value: 1, done: false }
console.log(g.next());// { value: 2, done: false }
console.log(g.next());// { value: 3, done: false }
console.log(g.throw("Throw Exception"));
//{ value: 'Throw Exception', done: false }
console.log(g.next());// { value: undefined, done: true }
yield* 키워드
 yield 에 * 를붙인다음 [expression] 에이터러블오브젝트를작성할
수있다. 이렇게 되면해당 yield 가 수행될때이터러블오브젝트를순회하
게 된다. 코드를통해살펴보자.
우선적으로배열을순회한후에, 다음에해당하는 yield 를수행하게 된다.
function* gen() {
yield 1;
yield* [10, 20, 30];
yield 2;
}
let g = gen();
console.log(g.next());// { value: 1, done: false }
console.log(g.next());// { value: 10, done: false }
console.log(g.next());// { value: 20, done: false }
console.log(g.next());// { value: 30, done: false }
console.log(g.next());// { value: 2, done: false }
console.log(g.next());// { value: undefined, done: true }
마무리
문법을아는것과 실제프로그래밍에서적용하는것은확실히다른문제이다.
지금  yield 의향연을보고 이걸 어디에다가 쓰나하는생각이들것이다. 다
음링크들을참고하면좀나아질것 같아서, 몇가지링크를첨부한다.
ES6의제너레이터를사용한비동기 프로그래밍
Javascript의Generator와Koa.js
자바스크립트와비동기 오류처리
Reference
MDN function*
10. end

[ES6] 10. Generator