Generator 오브젝트 -ECMAScript
함수를 호출하면 함수 블록의 코드를 한 번에 실행하지만,
제너레이터(Generator
)오브젝트는 나누어서 실행할 수 있습니다.
- Generator
제너레이터 함수 작성 형식은 3가지가 있습니다.
- function* 선언문
- function* 표현식
- GeneratorFunction
function*
선언문과 표현식은 기존의 function
과 형태가 같습니다,
“*”를 사용하는 형태만 다릅니다.
GeneratorFunction
은 new Function()
과 같으며
파라미터에 지정한 문자열로 제너레이터 함수를 생성하여 반환합니다.
중요 포인트
제너레이터 함수를 호출하면 제너레이터 오브젝트를 생성하여 반환합니다.
function()을 호출하면 함수 블록을 실행하지만, 제너레이터 함수는 함수 블록을 실행하지 않고 제너레이터 오브젝트를 생성하여 반환합니다.
생성한 제너레이터 오브젝트에 호출한 함수에서 넘겨 준 파라미터 값이 설정됩니다.
생성된 제너레이터 오브젝트는 이터레이터 오브젝트입니다.
이터레이터 오브젝트의 메서드를 호출했을 때 제너레이터 함수 블록을 실행합니다.
제너레이터 함수 블록에 yield
키워드를 작성하면 함수블록의 코드를 모두 실행하지 않고 yield
키워드 단위로 나누어 실행합니다.
제너레이터 함수는 new
연산자를 사용할 수 없으며 사용시 TypeError
가 발생합니다.
function* 선언문
선언문 형태로 제너레이터 함수를 정의합니다.
function* name([param[, param[, … param]]]) {
statements
}
name
함수명.param
함수에 전달되는 인수의 이름. 함수는 인수를 255개까지 가질 수 있다.statements
함수의 본체를 구성하는 구문들.반환 값
Generator 객체를 반환합니다.
1 | 1. function* sports(one, two){ |
function* sports(one, two){}
형태를 제너레이터 선언문이라고 합니다.
처음 제너레이터 함수를 호출하면서 넘겨주는 파라미터 값이sports()
의 파라미터에 작성한 이름(one, two)에 설정됩니다.
console.log(typeof sprts)
제너레이터 함수의typeof
는function
입니다.
let genObj = sports(1,2)
sports(1,2)
로 호출하면sports
함수 블록을 수행하지 않고 제너레이터 오브젝트를 생성하여 반환합니다. 함수 블록을 수행하면 console.log(“함수 블록”)이 실행되어야 하는데 실행되지 않습니다.sports(1,2)
에서 넘겨 준 파라미터 값이function* sports(one,two){}
의 파라미터 one 과 two에 설정됩니다. 따라서 제너레이터 오브젝트를 사용하여 제너레이터 함수를 호출했을 때 추가 처리를 하지 않아도 파라미터 값을 사용할 수 있습니다.
- 생성된 제너레이터 오브젝트의 type 인 object가 출력됩니다.
Generator 설명
Generator
는 빠져나갔다가 나중에 다시 돌아올 수 있는 함수입니다. 이때 컨텍스트(변수 값)는 출입 과정에서 저장된 상태로 남아 있습니다.
Generator
함수는 호출되어도 즉시 실행되지 않고, 대신 함수를 위한Iterator
객체가 반환됩니다.Iterator
의next()
메서드를 호출하면Generator
함수가 실행되어yield
문을 만날 때까지 진행하고,
해당 표현식이 명시하는Iterator
로부터의 반환값을 반환합니다.
yield*
표현식을 마주칠 경우, 다른Generator
함수가 위임(delegate
)되어 진행됩니다.
- 이후
next()
메서드가 호출되면 진행이 멈췄던 위치에서부터 재실행합니다.
next()
가 반환하는 객체는yield
문이 반환할 값(yielded value
)을 나타내는value
속성과,Generator
함수 안의 모든yield
문의 실행 여부를 표시하는boolean
타입의done
속성을 갖습니다.
next()
를 인자값과 함께 호출할 경우, 진행을 멈췄던 위치의yield
문을next()
메서드에서 받은 인자값으로 치환하고 그 위치에서 다시 실행하게 됩니다.
function* 표현식
표현식 형태로 제너레이터 함수를 정의합니다.
function* [name]([param1[, param2[, …, paramN]]]) {
statements
}
name
함수명. 생략하면 익명 함수가 됩니다. 함수명은 함수내에만 한정됩니다.paramN
함수에 전달되는 인수의 이름. 함수는 최대 255 개의 인수를 가질 수 있습니다.statements
함수의 본체를 구성하는 구문들.반환 값
Generator 객체function* expression
은function* statement
과 매우 유사하고 형식도 같습니다.function* expression
과function* statement
의 주요한 차이점은 함수명으로,function* expressions
에서는 익명 함수로 만들기 위해 함수명이 생략될 수 있습니다.
1 | 1. let sports = function*(one, two){ |
function
이름이 없는 무명(혹은 익명) 함수입니다.
함수를 변수에 할당해 줌으로써sports
를 함수 이름으로 사용할 수 있습니다.function_에 직접 함수 이름을 작성할 수 있지만, 외부에서 함수를 호출할 때는 sports()로 호출해야 합니다.
function_ 함수 이름은 함수 안에서 자신을 호출할 때 사용됩니다.(재귀 함수 호출)하지만 변수에 할당하며 작성한 함수 이름으로 재귀 호출할 수 있으므로
function* 에 직접 함수이름을 작성하는 방법은 잘 사용하지 않습니다.
자바스크립트 초기 버전에서 사용했습니다.
sports(10, 20)
으로 호출하여 제너레이터 오브젝트를 생성하고, 10을 파라미터 one에 설정하고 20을 two에 설정합니다.
이때, 함수 블록의 코드를 실행하지 않고 생성한 제너레이터 오브젝트를 반환합니다.
- 생성된 제너레이터 오브젝트는 이터레이터 오브젝트입니다. 제너레이터 오브젝트의
next()
를 호출하면 이터레이터 오브젝트와 같은 처리를 수행합니다.next()
를 호출하여sports
제너레이터 함수의 함수 블록을 수행합니다.
- 제너레이터 함수 블록의 코드 입니다.
console
에 “함수 블록”을 출력합니다.sports(10, 20)
으로 호출했을 때,one
과two
에 값을 설정했으므로yield
키워드에서 파라미터 이름으로 값을 구할 수 있습니다.yield
키워드는yield
오른쪽의 표현식을 평가하고,
평가 결과를{value: 30, done: false}
형태로 반환합니다.
개발자 도구에서 sports 제너레이터 함수 (== 크롬브라우저)
엔진이 function*
키워드를 만나면 제너레이터 함수 오브젝트를 생성하여 sports
변수에 할당 합니다.
sports(10, 20)
을 호출하면 sports.prototype
에 연결된 프로퍼티로 제너레이터 오브젝트를 생성하여 반환합니다.
__proto__.constructor는 생성자 함수로 이름이 GeneratorFunction입니다.
반환된 제너레이터 오브젝트에 __proto__.__proto__에 next()가 있으므로genObj.next()
형태로 호출할 수 있습니다.
GeneratorFunction(): 제너레이터 함수 생성
제너레이터 함수를 생성하여 반환합니다.
GeneratorFunction
생성자는 새로운 generator function
객체를 생성합니다. JavaScript
에서 모든 generator function
은 실제로 GeneratorFunction object
입니다.
주의할 점은, GeneratorFunction
이 전역 객체(global object
)가 아니란 점입니다.
GeneratorFunction
은 다음의 코드를 실행해서 얻을 수 있습니다.
Object.getPrototypeOf(function*(){}).constructor
new 연산자로 GeneratorFunction() 함수를 호출할 수 없습니다.
이름 없는 제너레이터 함수를 생성하고, 여기에 연결된 constructor를 사용하여 제너레이터 함수를 생성합니다.
1 | 1. let GenConst = Object.getPrototypeOf(function*(){}).constructor; |
- 제너레이터 함수를 생성하기 위한 생성자(
constructor
)를 구합니다.function*(){}
으로 익명 제너레이터 함수를 생성하여Object.getPrototypeOf()
의 파라미터 값으로 지정합니다.
익명 제너레이터 함수의prototype
오브젝트가 반환됩니다.prototype
에constructor
가 있으므로 이를 반환하고GenConst
에 할당합니다. (Genconst 변수가 생성자 함수가 되는 것 입니다.)
new
연산자로GenConst
생성자를 호출하여 제너레이터 함수를 생성합니다.
(GenConst의 파라미터에 생성될 제너레이터 함수에서 사용할 파라미터와 함수 블록 코드를 문자열로 작성합니다.)첫 번째
one
과 두 번째two
가 제너레이터 함수의 파라미터가 되고,
세 번째 파라미터가 함수 블록 코드가 됩니다.
파라미터의 문자열을 parsing(문자열 해석?)하면 다음과 같은 형태가 되는 것입니다.
1 | (function*(one,two){ |
sports(3,4)
로 호출하면 제너레이터 오브젝트를 생성하여 반환합니다.function*(one, two){}
에서 3이one
에 4가two
에 설정됩니다.
next()
를 호출하면 함수 블록 코드를 실행합니다.Object {value: 7, done: false}
를 반환합니다.
yield: 제너레이터 함수 실행, 멈춤
yield
키워드는 제너레이터 함수를 멈추게 하거나 다시 실행하는데 사용됩니다.
[returnValue] = yield [expression];
- expression
제너레이터 함수에서 제너레이터 프로토콜을 통해 반환할 값을 정의합니다(표현식). 값이 생략되면, undefined를 반환합니다.
- returnValue
제너레이터 실행을 재개 하기 위해서, optional value을 제너레이터의 next() 메서드로 전달하여 반환합니다.
- yield의 표현식 평가 결과를 왼쪽의 returnValue에 할당하지 않습니다.
제너레이터 오브젝트의 next()를 호출하면 next() 파라미터 값이 returnValue에 할당됩니다.
- next()로 제너레이터 함수를 호출하면 yield 작성에 관계없이 “{value: 값, done: false/true}” 형태로 반환합니다.
- yield를 수행하면 표현식 평가 결과가 value 값에 설정되고, yield를 수행하지 못하면 undefined가 설정됩니다.
1 | function* sports(one){ |
sports(10)
으로 호출하여 제너레이터 함수의one
파라미터에 10이 설정되고
제너레이터 오브젝트를 생성하여 반환합니다. (함수 블록의 코드는 실행하지 않습니다.)
generatorObj.next()
를 호출하면sports
제너레이터 함수 블록의 첫 줄부터 첫 번째 ``yield까지 수행합니다. == (let two = yield one;)
yield의 표현식 평과 결과
{value: 10, done: false}형태를 반환합니다. 할당연산자(
=)는 오른쪽 값을 왼쪽 변수에 할당하지만,
yield의 할당연산자(
=`)는 할당하지 않습니다.
next()
를 다시한번 호출하면 파라미터 값을 (let two = yield one)에서 two 변수에 설정합니다. 파라미터 값이 지정되지 않았으며undefined
가 two 변수에 설정됩니다. 그리고 아래 코드를 실행합니다.two
변수에undefined
가 설정되어 있고,one
변수에 10이 설정되어 있으므로Object {value: NaN, done: false}
를 반환합니다. NaN을 param 변수에 설정하지 않습니다.
next(20)
으로 호출하면 파라미터 값 20을(let param = yield two + one)
에서param
변수에 설정합니다.param
변수 값이 20이고one
변수 값이 10이므로{value: 30, done: false}
를 반환합니다.
1 | function* sports(one){ |
sports(10)
으로 호출하면 제너레이터 함수의one
파라미터에 10이 설정되고
제너레이터 오브젝트를 생성하여 반환합니다.
next()
를 호출하면 함수 블록의 첫 줄부터 첫 번째yield
의 표현식까지 수행합니다.yield one; = {value: 10, done: false}
형태로 반환합니다.
next()
를 호출하면 파라미터 값을 바로 앞yield
왼쪽에 있는 변수에 할당합니다.
그런데 왼쪽의 변수가 없으므로 값을 할당하지 않습니다.
이후에 아래 코드를 실행합니다.
check
에 10을 할당하는 코드이지만 더 이상 수행할yield
는 없고
함수 안에 더 처리해야할 코드도 없습니다. 반환할 값이 없습니다.value
프로퍼티 값에undefined
를 설정하고,done
프로퍼티 값에true
를 설정합니다.{value: undefined, done: true}
형태로 반환됩니다.
next(): yield 단위로 실행
제너레이터 함수에서 yield
단위로 실행합니다.
next()
를 호출하면 yield
를 기준으로 이전 yield
의 다음 줄부터 yield
까지 수행합니다.
제너레이터 함수에 yield
가 여러개 작성되어 있으면, yield
수만큼 next()
를 작성해줘야 제너레이터 함수 전체를 실행하게 됩니다.
파라미터는 선택으로 제너레이터 함수가 멈춘 yield
의 왼쪽 변수에 설정합니다.
1 | let gen = function*(value){ |
gen(1)
로 호출하면 제너레이터 함수의 value 파라미터에 1이 설정되며 제너레이터 오브젝트를 생성하여 반환합니다.
- next()를 호출하면 제너레이터 함수 첫 줄부터 yield의 표현식까지 수행합니다.
즉, 다음 코드를 실행합니다.value = value + 10;
yield ++value;
파라미터로 받은 1에 10을 더해 value에 할당합니다.
다음으로yield ++value;
를 실행하여 value값에 1을 더합니다.{value: 12, done: false}
가 반환됩니다.
- 다시 next()를 호출하면
yield ++value
에서yield
왼쪽에 파라미터 값을 설정합니다. 그런데 왼쪽에 변수가 없으므로 다음 코드를 실행합니다.value = value + 7;
yield ++value;
value 변수 값이 12이므로 7을 더해 19를 value에 할당합니다.
다음으로yield ++value;
을 실행하여{value: 20, done: false}
를 반환합니다.
- 다시 next()를 호출하면 제너레이터 함수에
yield
가 없으므로{value: undefined, done: false}
를 반환합니다.
next() 활용 예제
청구 금액과 할인 금액 계산하여 반환
청구 금액을 계싼하는 제너레이터 함수와 할인 금액을 계산하는 일반 함수를 정의합니다.
청구 금액 계산 제너레이터 함수는 수량과 단가를 파라미터로 받아 금액을 계산합니다.
계산한 금액을 yield로 반환합니다.
할인 금액 함수를 호출하면서 yield로 반환된 값을 파라미터 값으로 넘겨 줍니다.
파라미터의 금액에 따라 할인 금액을 계산하여 반환합니다.
청구 금액 계산 제너레이터 함수를 호출하면서 할인 금액을 파라미터로 넘겨줍니다.
합계 금액에서 할인 금액을 빼서 청구 금액을 계산합니다,
계산된 청구 금액을 반환합니다.
1 | // 청구 금액을 계산하는 제너레이터 함수 getAmount |
next()의 다양한 형태
next()와 yield를 조합한 다양한 형태를 살펴 봅니다.
제너레이터 함수를 계속 호출 하려면 yield가 이에 대응할 수 있어야 합니다.
이를 위해 한 줄에 다수의 yield를 작성할 수도 있고,
배열 안에 다수의 yield를 작성할 수도 있습니다.
next-while
1 | let gen = function*(value) { |
제너레이터 함수에 while
문을 작성하고 그 안에 yield
를 작성한 형태입니다.
- 처음
next()
를 호출하면gen()
함수의 첫 줄부터yield
까지 수행한 후 그 값을 반환합니다.(let count = 0;)
는 처음 한 번만 실행되고 다음부터는 실행하지 않습니다.while(value){}
에서 value 파라미터 값이 true이므로 블락을 실행합니다.yield ++count
가 실행되어{value: 1, done: false}
를 반환합니다.
- 다시
next()
를 호출하면 앞에서 증가된count
변수 값이 유지되므로 값을 누적할 수 있습니다.yield ++count
를 실행하여{value: 2, done: false}
를 반환합니다.
이와 같이while
문 안에yield
를 작성하면next()
를 호출할 때마다yield
가 수행됩니다.
next-return-yield
1 | let gen = function*(){ |
제너레이터 함수의 return
문에 다수의 yield
를 작성한 형태입니다.return
문의 표현식에 yield
가 3
개 작성되어 있습니다.
- 처음
next()
를 호출하면 첫 번째yield
를 수행합니다.yield
에 반환 값이 없으므로{value: undefined, done: false}
를 반환합니다.
- 두 번째로 next(10)을 호출하면 두 번째 yield를 수행합니다.
왼쪽에 파라미터 값을 받을 변수가 없으므로 파라미터로 넘겨준 값 그대로 반환합니다. 따라서{value: 10, done: false}
를 반환합니다.
- 세 번째로 next(20)을 호출하면 세 번째 yield를 수행합니다.
{value: 20, done: false}
를 반환합니다.
- 마지막으로
next(30)
을 호출하면 수행할 yield가 없습니다.{done: true}
를 반환하고,return
을 작성했으므로 파라미터로 넘겨준 값을
반환 합니다. 즉{value: 30, done: false}
이 반환됩니다.return
을 작성하지 않으면{value: undefined, done: true}
를 반환합니다.
next-array-yield
1 | let gen = function*(){ |
제너레이터 함수의 return
문에 배열안에 다수의 yield
를 작성한 형태입니다.
- 는
return
문에yield
을 연속으로 작성한 것과 같습니다.yield
가 2개 뿐이므로 더 이상 수행할yield
가 없는 상태가 됩니다.
- 는
마지막으로 next(20)을 호출하면 (return [yield yield])에서
yield
를 제외한
[]안에 파라미터로 넘겨준 값을 작성합니다.{value: Array[1], done: true}
형태로 반환됩니다.Array[1]
은{0:20}
입니다.
for-of-yield
1 | let gen = function*(start){ |
for-of()
문에 제너레이터 함수를 호출하는 코드를 작성한 형태입니다.
- 처음
for-of
문을 시작하면gen(10)
을 호출하여 제너레이터 함수의start
파라미터에 10을 설정하고 제너레이터 오브젝트를 생성하여 반환합니다.
호출한gen(10)
위치로 돌아오면 반환받은 오브젝트를 할당할 변수가 없으므로 엔진 내부에 저장합니다.
gen()
함수를 호출하며 이는next()
를 호출한 것과 같습니다.let value = start;
를 수행한 후while (true){yield ++value;}
를 수행합니다.{value: 11, done:false}
를 반환하게 되며value
프로퍼티 값이count
변수에 설정됩니다.
for-of
문의 블록에 작성한 코드를 수행하며 콘솔에 11이 출력됩니다.count
변수 값이 11이므로 다시for-of
문을 반복하게 됩니다.count
변수 값이 12보다 클때 까지for-of
문을 반복합니다.
throw(): Error 발생
throw()
메서드는 Generator
의 실행을 재개시키고 Generator
함수의 실행 문맥 속으로 error
를 주입합니다.
제너레이터 함수를 호출하여 받은 제너레이터 오브젝트의 throw()
를 호출하면
에러가 발생합니다. 에러가 발생하면 제너레이터 함수의 catch()
문에서 에러를 받습니다.
1 | let gen = function*(){ |
next()
를 호출하면try
문 의(yield 10;)
을 실행합니다.
에러가 발생하지않으며{value: 10, done: false}
를 반환합니다.
throw(“에러 발생”)
를 호출하면 제너레이터 함수의catch(message)
가 실행됩니다.throw()
의 파라미터 값이catch(message)
의message
파라미터에 설정됩니다.catch()
블록{}의(yield message;)
가 실행되어{value: “에러 발생”, done: false}
가 반환됩니다.
중요한 점은done: false
를 반환한다는 점입니다. 즉, 에러는 발생했지만 다음에 next()를 호출할 수 있습니다.- genObj.throw(Error(“에러 발생”)); 과 같이 파라미터에 Error 오브젝트를 작성할 수도 있습니다.
- 앞에서
throw()
를 호출하며catch()
블록을 수행했지만 이터레이터가 종료된 것은 아닙니다.
따라서 다시next()
를 호출할 수 있으며 함수의(yield 20;)
을 수행하여{value: 20, done: false}
를 반환합니다.
제너레이터 함수에서 에러가 발생
1 | let gen = function*(){ |
try
문에서next()
를 호출하면 제너레이터 함수 첫 줄에서throw
문으로 인해 에러가 발생하며,catch(error)
에서 에러를 받습니다.
(throw “에러 발생”)에서 “에러 발생”이 catch(error)의 error 파라미터에 설정됩니다.
- 제너레이터 함수에서 에러가 발생하면 이터레이터가 종료됩니다.
따라서next()
를 실행하면 제너레이터 함수내에throw “에러 발생”
밑에
yield 20;이 있지만 실행되지 않습니다.{value: undefined, done: true}
가 반환됩니다.
yield* 키워드
yield* [[expression]]
표현식(expression
)에 이터러블 오브젝트를 작성합니다. next()
를 호출할 때 마다
이터러블 오브젝트를 하나씩 실행하며, 결과 값을 yield
의 반환 값으로 사용합니다.
표현식에 제너레이터 함수를 작성할 수 있습니다.
표현식으로 호출된 함수에 다수의 yield
가 있으면 호출된 함수의 yield
를 전부 처리한 후 yield*
아래에 작성한 코드를 실행합니다.
yield* 표현식에 제너레이터 함수 작성 형태
1 | let plusGen = function*(value) { |
console.log로
genObj.next()
를 호출하면 실행되는 순서가 다음과 같습니다.gen()
함수의yield* plusGen(value)
을 실행합니다.yield*
를 작성했으므로plusGen(value)
을 호출하면서 파라미터 값으로 10을 넘겨줍니다.plusGen()
이 제너레이터 함수이므로 제너레이터 오브젝트를 생성하여 반환합니다.next()
로 호출헤야plusGen()
함수의yield
를 수행하지만, 이때는 자동으로plusGen()
의 첫 번째(yield value + 5)
를 수행하며{value: 15, done: false}
를 반환합니다.plusGen()
을 호출한 곳에서 다시yield
를 수행하므로plusGen()
에서 반환된 값을 반환합니다.콘솔에
Object {value: 15, done: false}
를 출력합니다.
next()
를 호출하면plusGen()
에서 수행하지 않은(yield value + 10;)
을 실행하며Object {value: 20, done: false}
를 반환합니다.
next()
를 호출하면plusGen()
에 더 이상 실행할yield
가 없으므로plusGen()
을 호출한gen()
함수 내의 코드 아래의 코드를 수행합니다.(yield value + 20;)
을 실행하게 되며Object {value: 30, done: false}
를 반환합니다.
yield* 표현식 재귀 호출 형태
1 | let gen = function*(value) { |
- 처음
next()
를 호출하면 제너레이터 함수 첫째 줄의(yield value;)
를 실행하며{value: 1, done: false}
를 반환합니다.
- 두 번째로
next()
를 호출하면(yield* gen(value + 10);)
을 실행합니다.
그런데yield*
표현식에서 자신 함수(gen
)를 호출합니다. 파라미터 값으로 11을 넘겨주며 next()가 없지만 엔진에서 반환받은 오브젝트의 제너레이터 함수를 호출합니다. (yield value;)가 실행되며{value: 11, done: false}
를 반환합니다.
이때, yield value;가 없다면 계속해서 자신을 호출하게 되므로 무한 루프를 돌게됩니다.
- 세 번째로
next()
를 호출하면(yield* gen(value + 10);)
을 실행하고
자신을 호출합니다. 위와 같이 진행되어{value: 21, done: false}
를 반환합니다.