Generator
Generator는 ES6에서 도입되었으며 Iterable을 생성하는 함수다. Generator를 사용하면 Iteration Protocol을 사용하여 Iterable을 생성하는 방식보다 간편하다.
Iteration Protocol에 대한 자세한 내용은 다음을 참고하길 바란다.
Generator 함수의 정의
Generator 함수는 코드 블록을 한 번에 실행하지 않고 함수 코드 블록의 실행을 중지했다가 필요한 시점에 다시 시작할 수 있는 함수다.
Generator 함수는 function * 키워드를 사용하며 하나 이상의 yield 문을 포함한다.
// 함수 선언문
function* decFunc() {
yield 1;
}
let genObj = decFunc();
// 함수 표현식
const expFunc = function*() {
yield 1;
};
genObj = expFunc();
// 메서드
const obj = {
*objectMethod() {
yield 1;
}
};
genObj = obj.objectMethod();
// 클래스 메서드
class GenClass {
*classMethod() {
yield 1;
}
}
const genClass = new GenClass();
genObj = genClass.classMethod();
Generator 객체
Generator 함수를 호출하면 코드 블록이 실행되는 것이 아니라 Generator 객체를 반환한다. Generator 객체는 이터러블이면서 동시에 이터레이터다. 따라서 Symbol.iterator를 사용하여 이터레이터를 생성할 필요 없다.
function* counter() {
console.log("First");
yield 1;
console.log("Second");
yield 2;
console.log("Third");
yield 3;
console.log("The end");
}
const genObj = counter();
console.log(genObj.next()); //{value: 1, done: false}
console.log(genObj.next()); //{value: 2, done: false}
console.log(genObj.next()); //{value: 3, done: false}
console.log(genObj.next()); //{value: undefined, done: true}
Generator 객체는 이터러블이면서 이터레이터이기 때문에 next()메서드를 가지고 있다. 따라서 next() 메서드를 호출하면 yield문까지 실행되고 일시 중지된다. 다시 next() 메서드를 호출하면 다음 yield문을 만날 때까지 실행된 뒤 일시 중지된다.
Generator 객체를 이용한 이터러블 구현
const genObj = (function*() {
let i = 0;
while (true) {
yield ++i;
}
})();
for (let item of genObj) {
if (item === 10) break;
console.log(item);
}
// Generator 함수에 파라미터 전달
const genObj = function*(max) {
let i = 0;
while (true) {
if (i === max) break;
yield ++i;
}
};
for (let item of genObj(10)) {
console.log(item);
}
// next 메서드에 파라미터 전달
function* genFunc(n) {
let res;
res = yield n;
console.log(res);
res = yield res;
console.log(res);
res = yield res;
console.log(res);
return res;
}
const genObj = genFunc(0);
console.log(genObj.next());
console.log(genObj.next(1));
console.log(genObj.next(2));
console.log(genObj.next(3));
Generator를 이용한 비동기 처리
Generator의 진면목은 비동기 프로그래밍에서 볼 수 있다. 함수가 실행 도중에 멈춘다니. 언제 응답이 올지 알 수 없기 때문에, callback을 등록하는 비동기 프로그래밍에 응용하면 callback hell을 탈출할 수 있지 않을까?
function getId(phoneNumber) {
// …
iterator.next(result);
}
function getEmail(id) {
// …
iterator.next(result);
}
function getName(email) {
// …
iterator.next(result);
}
function order(name, menu) {
// …
iterator.next(result);
}
function* orderCoffee(phoneNumber) {
const id = yield getId(phoneNumber);
const email = yield getEmail(id);
const name = yield getName(email);
const result = yield order(name, "coffee");
return result;
}
const iterator = orderCoffee("010-1234-1234");
iterator.next();
Generator는 어떻게 구현되어 있을까?
// ES6
function* foo() {
yield bar();
}
// ES5 Compiled
("use strict");
var _marked = /*#__PURE__*/ regeneratorRuntime.mark(foo);
function foo() {
return regeneratorRuntime.wrap(
function foo$(_context) {
while (1) {
switch ((_context.prev = _context.next)) {
case 0:
_context.next = 2;
return bar();
case 2:
case "end":
return _context.stop();
}
}
},
_marked,
this
);
}
Genrator는 결국 iterable Protocol를 구현하는 객체이다. 그러나 프로토콜과 관련된 어느것도 보이지 않는다.
대신 regeneratorRuntime이 보인다.
babel에서는 regeneratorRuntime라이브러리를 사용해서 구현을 했다.
코드의 역사를 따라가다 보면 facebook/regenerator repository에 도달하게 된다.
이 라이브러리는 2013년 Node.js v0.11.2에서 generator syntax를 지원하기 위해 만들어 졌으며, Babel에서는 이 라이브러리를 사용하여 generator를 구현하고 있다. 실제 코드를 들여다보면 Symbol과 Iterator를 이용해서 Iterable Protocol을 구현하고 있다.