Javascript/[인프런] 자바스크립트 ES6+ 기본
[자바스크립트 ES6+ 기본] 17. Generator 오브젝트
minha62
2022. 2. 17. 04:40
Generator 함수
function*
- Generator function
- function* 키워드를 사용한 함수
- 제너레이터 함수 형태
- function 선언문, function 표현식, GeneratorFunction
function* sports(one){
};
const book = function*(one){
};
const music = Object.getPrototypeOf(
function* (){}).constructor;
const gen = new music();
- 작성 방법
- function* 다음에 소괄호() 작성. 이어서 작성해도 되고 하나 이상 띄워도 됨
function* 선언문
- function* 다음에 함수 이름 작성
- 제너레이터 함수를 호출하면
- 함수 블록{}을 실행하지 않고
- Generator 오브젝트를 생성하여 반환
function* sports(one, two){
yield one + two;
};
log(typeof sports);
const obj = sports(1, 2);
log(typeof obj);
log(obj.next());
// function
// object
// {value: 3, done: false}
- Generator 오브젝트는 iterator 오브젝트
- 함수 코드 실행
- Generator 오브젝트의 메소드를 호출할 때
function* 표현식
- function* 다음에 함수 이름 작성은 선택
- 일반적으로 함수 이름을 작성하지 않음
- function* 왼쪽에 변수를 선언하며 변수 이름이 함수 이름이 됨
const sports = function* (one){
yield one;
};
const obj = sports(100);
log(obj.next());
// {value: 100, done: false}
- 함수를 선언하는 형태만 다를 뿐
- 다른 것은 function* 선언문과 같음
GeneratorFunction
- GeneratorFunction.constructor를 사용하여
- 제너레이터 함수를 생성
- 파라미터를 문자열로 작성
- 마지막 파라미터가 함수 코드가 되고 앞은 파라미터 이름이 됨
const fn = new Function("one", "return one");
log(fn(100));
const create = Object.getPrototypeOf(
function*(){}).constructor;
const sports = new create("one", "yield one");
const obj = sports(100);
log(obj.next());
// 100
// {value: 100, done: false}
- 제너레이터 함수 구조
const gen = function*(){};
/*
1. 오른쪽의 gen을 펼치현 prototype이 있음
- 이것을 펼치면 constructor가 있어야 하는데 없음
- 메소드도 없음
2. __proto__가 있으며 이것을 펼치면 constructor가 있음
- __proto__에 다른 오브젝트의 prototype에 연결된 프로퍼티를 인스턴스 개념으로 생성하여 첨부한 것이 표시됨
3. 즉, GeneratorFunction의 constructor가 첨부된 것
*/
const create = Object.getPrototypeOf(
function*(){}).constructor;
log(create);
const sports = new create("one", "yield one;");
log(typeof sports);
const obj = sports(100);
log(obj.next());
// function GeneratorFunction() { [native code] }
// function
// {value: 100, done: false}
- create = (function*(){}).constructor;
- 제너레이터 함수를 생성하는 constructor(생성자)를 할당
- constructor가 할당되므로
- new 연산자로 생성자 함수를 호출할 수 있음
- log(create);
- function GeneratorFunction(){} 출력
- function 오브젝트 형태
- sports = new create(param)
- GeneratorFunction을 사용하여 제너레이터 함수를 생성하고 sports 변수에 할당
- param에 파라미터와 함수 코드를 작성
- one: 파라미터 이름
- yield one: 함수 코드
- log(typeof sports)
- new 연산자를 사용했는데
- sports가 object가 아닌 function
- function이라는 것은
- function* sports()로 제너레이터 함수를 선언한 것을 뜻함
- 즉, 지금까지 제너레이터 함수를 선언하는 처리를 한 것
- const obj = sports(100);
- 제너레이터 함수를 호출
- 제너레이터 오브젝트 생성, 반환
- 함수 코드를 실행하지 않음
- 100이 one에 매핑됨
- obj.next()
- 제너레이터 오브젝트는 이터레이터 오브젝트이며
- obj에 이터레이터 오브젝트가 할당되어 있으므로
- next()를 호출할 수 있음
- {value: 100, done: false} 출력
yield
Syntax: [returnValue] = yield [표현식];
- yield 키워드 사용 형태
- next()로 호출할 때마다 하나씩 실행
function* sports(one){
yield one + 10;
yield;
const value = yield one + 50;
};
const obj = sports(30);
log(obj.next());
log(obj.next());
log(obj.next());
log(obj.next(200));
// {value: 40, done: false}
// {value: undefined, done: false}
// {value: 80, done: false}
// {value: undefined, done: true}
- yield 키워드는
- 제너레이터 함수 실행을 멈추거나 다시 실행할 때 사용
- yield 오른쪽의 표현식을 평가하고 결과를 반환
- 표현식을 작성하지 않으면 undefined 반환
- [returnValue]
- 오른쪽의 평가 결과가 설정되지 않고
- 다음 next()에서 파라미터로 넘겨준 값이 설정됨
- yield 표현식을 평가하면
- 호출한 곳으로 {value: 값, done: true/false} 반환
function* sports(one){
yield one;
const check = 20;
};
const obj = sports(10);
log(obj.next());
log(obj.next());
// {value: 10, done: false}
// {value: undefined, done: true}
- value 값
- yield 표현식의 평가 결과 설정
- yield를 실행하지 못하면 undefined
- done 값
- yield를 실행하면 falase
- yield를 실행하지 못하면 true
yield 정리
function* sports(one){
let two = yield one;
let param = yield one + two;
yield param + one;
};
const obj = sports(10);
log(obj.next());
log(obj.next());
log(obj.next(20));
log(obj.next());
// {value: 10, done: false}
// {value: NaN, done: false}
// {value: 30, done: false}
// {value: undefined, done: true}
- function* sports(one){}
- 제너레이터 함수 선언
- 3개의 yield 작성함
- const obj = sports(10);
- 제너레이터 오브젝트 생성
- 파라미터 값, 10이 one에 설정됨
- 첫 번째의 obj.next()를 호출
- let two = yield one이 실행됨
- one의 값인 10을 반환
- two 변수에 10을 할당하지 않음
- 두 번째의 obj.next()를 호출
- next()에 파라미터 값을 작성하지 않았으므로 two 변수에 undefined가 설정됨
- let param = yield one + two를 실행
- two 변수 값이 undefined이므로 NaN 반환
- 세 번째의 obj.next(20)를 호출
- 파라미터 값 20이 바로 앞의 param 변수에 설정됨
- yield param + one 실행
- 20 + 10 반환
- 네 번째의 obj.next()를 호출
- 실행할 yield가 없으므로 더 이상 처리하지 않으며
- 끝이라는 것을 나타내는 done: true를 반환
next()
- next()는 yield 단위로 실행
- yield 수만큼 next()를 작성해야 yield 전체를 실행
- next()를 호출하면
- 이전 yield의 다음부터 yield까지 실행
function* sports(value){
value += 20;
const param = yield ++value;
value = param + value;
yield ++value;
};
const obj = sports(10);
log(obj.next());
log(obj.next(20));
// {value: 31, done: false}
// {value: 52, done: false}
- yield를 작성하지 않았을 때
function* sports(value){
++value;
log(value);
};
const obj = sports(10);
log(obj.next());
// 11
// {value: undefined, done: true}
- 제너레이터 함수에 return 문을 작성했을 때
function* sports(value){
return ++value;
};
const obj = sports(10);
log(obj.next());
log(obj.next());
// {value: 11, done: true}
// {value: undefined, done: true}
- 함수는 호출될 때마다 변수에 초기값을 설정
- 제너레이터 함수는
- 제너레이터 오브젝트를 생성할 때 초기값을 설정
- next()로 실행될 때마다 초기값을 설정하지 않음
- 변수값을 그대로 유지
yield 반복
let status = true;
function* sports(){
let count = 0;
while (status){
yield ++count;
};
};
const obj = sports();
log(obj.next());
log(obj.next());
status = false;
log(obj.next());
// {value: 1, done: false}
// {value: 2, done: false}
// {value: undefined, done: true}
- yield를 반복하는 형태
- let status = true;
- while() 문을 제어하기 위한 상태 값
- 첫 번째 next() 호출
- let count = 0;을 실행하여 count 변수에 0을 설정
- 누적 값을 구하기 위한 것
- while (status){ yield ++count; }
- status가 true이므로 yield를 수행
- {value: 1, done: false} 반환
- 두 번째 next()를 호출
- status가 true이므로 yield 수행
- {value: 2, done: false} 반환
- status = false;
- yield 수행을 끝내기 위한 것
- 세 번째 next() 호출
- status가 false이므로 yield ++count;를 수행하지 않음
- {value: undefined, done: true} 반환
- {done: true}이므로 이터레이터를 더 이상 사용할 수 없음
다수의 yield 처리
function* sports(){
return yield yield yield;
};
const obj = sports();
log(obj.next());
log(obj.next(10));
log(obj.next(20));
log(obj.next(30));
// {value: undefined, done: false}
// {value: 10, done: false}
// {value: 20, done: false}
// {value: 30, done: true}
- 한 줄에 다수의 yield와 return 작성
- return yield yield yield;
- 첫 번째 next() 호출
- 첫 번째 yield 수행
- yield에 반환 값이 없으므로 {value: undefined, done: false} 반환
- 두 번째 next(10) 호출
- 파라미터 값: 10
- 두 번째 yield 수행
- 함수에 파라미터 값을 받을 변수가 없으면 파라미터로 넘겨 준 값을 반환
- {value: 10, done: false} 반환
- 세 번째 next(20) 호출
- 파라미터 값: 20
- 세 번째 yield 수행
- 함수에 파라미터 값을 받을 변수가 없으므로 파라미터로 넘겨 준 값을 반환
- {value: 20, done: false} 반환
- 네 번재 next(30) 호출
- 파라미터 값: 30
- 처리할 yield가 없으므로 done: true 반환
- return문을 작성했으므로 파라미터로 넘겨 준 값을 반환
- {value: 30, done: true} 반환
- return 문을 작성하지 않으면
- 30이 아닌 undefined 반환
- {value: undefined, done: true} 반환
yield 분할 할당
function* sports(){
return [yield yield];
};
const obj = sports();
log(obj.next());
log(obj.next(10));
const last = obj.next(20);
log(last);
log(last.value);
// {value: undefined, done: false}
// {value: 10, done: false}
// {value: [20], done: true}
// [20]
- 대괄호[] 안에 다수의 yield 작성
- return [yield yield];
- next(), next(10) 호출
- [실행 결과]에서 보듯이 yield를 연속해서 작성한 것과 같음
- yield를 2개 모두 수행했으므로 더 이상 처리할 yield가 없음
- 세 번째 next(20) 호출
- 파라미터 값: 20
- return [yield, yield]에서 {value: [20], done: true} 형태로 반환
- [20]처럼 [] 안에 파라미터 값 20을 넣어서 반환
- console.log()에 {value: Array(1)} 형태로 표시되지만 가독성을 위해 편집함
for-of 문으로 반복
function* sports(count){
while(true){
yield ++count;
};
};
for (let point of sports(10)){
log(point);
if(point > 12){
break;
};
};
// 11
// 12
// 13
- for-of 문으로 제너레이터를 반복 호출
- 처음 for-of문을 시작하면
- sports(10)으로 제너레이터 오브젝트 생성
- 제너레이터 오브젝트에 10이 설정됨
- 생성한 제너레이터 오브젝트를 저장할 변수가 없으며 엔진 내부에 저장함
- const engine = sports(10);과 같으며 engine이 엔진 내부의 이름으로 가정
- 다시 sports*() 호출
- engine.next()와 같지만 반환 값이 다름
- while(true){ yield ++count } 실행
- {value: 11, done: false}를 반환하지 않고 value만 point 변수에 설정
- {done: true}로 종료 처리를 할 수 없으므로
- break;를 사용하여 종료시켜야 함
- for-of 블록을 실행
- 11 출력
- value 값이 11이므로 다시 for-of 문을 수행하며
- while(true){ yield ++count }를 수행
- 이렇게 break;를 만날 때까지
- 반복하여 yield ++count; 실행
제너레이터 오브젝트 메소드
return()
- 이터레이터를 종료시킴
function* sports(count){
while(true){
yield ++count;
};
};
const obj = sports(10);
log(obj.next());
log(obj.return(70));
log(obj.next(50));
// {value: 11, done: false}
// {value: 70, done: true}
// {value: undefined, done: true}
- return()의 파라미터 값을
- {value: 값, done: true}에서 value 프로퍼티 값으로 설정
throw()
- Error를 의도적으로 발생시킴
- 제너레이터 함수의 catch()문에서 에러를 받음
function* sports(){
try{
yield 10;
} catch (message){
yield message;
};
yield 20;
};
const obj = sports();
log(obj.next());
log(obj.throw("에러 발생"));
log(obj.next());
// {value: 10, done: false}
// {value: 에러 발생, done: false}
// {value: 20, done: false}
- 제너레이터 함수에 throw 문을 작성
function* sports(){
throw "에러 발생";
yield 10;
};
const obj = sports();
try {
const result = obj.next();
} catch (message){
log(message);
};
log(obj.next());
// 에러 발생
// {value: undefined, done: true}
yield* 표현식
Syntax: yield* 표현식
- yield*의 표현식에 따라 처리하는 방법이 다름
1) yield*의 표현식이 배열
- next()로 호출할 때마다 배열의 엘리먼트를 하나씩 처리
function* sports(){
yield* [10, 20];
};
const obj = sports();
log(obj.next());
log(obj.next());
// {value: 10, done: false}
// {value: 20, done: false}
2) yield*의 표현식이 제너레이터 함수
- 함수의 yield를 먼저 처리
function* point(count){
yield count + 5;
yield count + 10;
};
function* sports(value){
yield* point(value);
yield value + 20;
};
const obj = sports(10);
log(obj.next());
log(obj.next());
log(obj.next());
// {value: 15, done: false}
// {value: 20, done: false}
// {value: 30, done: false}
- 첫 번째의 obj.next()를 호출하면
- yield* point(value); 실행
- yield*의 표현식에 함수를 작성했으므로
- point(value) 호출
- point()가 제너레이터 함수이므로
- 우선, 제너레이터 오브젝트 생성
- next()로 호출해야 yield가 수행되지만
- 자동으로 point() 첫 번째의 yield count + 5;를 수행
- {value: 15, done: false} 반환
- 다시 point()를 호출한 곳에서 반환 값을 받아 반환
- 두 번째의 obj.next()를 호출
- point()의 yield count + 10;를 실행
- {value: 20, done: false} 반환
- 세 번째의 obj.next() 호출
- point()의 yield를 모두 처리했으므로
- sports()의 yield value + 20;을 실행
3) yield* 표현식에서 자신 호출 (재귀 호출)
function* sports(point){
yield point;
yield* sports(point + 10);
};
const obj = sports(10);
log(obj.next());
log(obj.next());
log(obj.next());
// {value: 10, done: false}
// {value: 20, done: false}
// {value: 30, done: false}
- 첫 번째의 obj.next()를 호출하면
- yield point; 실행
- {value: 10, done: false} 반환
- 두 번째의 obj.next() 호출
- yield* sports(point + 10);에서 자신을 호출
- 첫 번째 줄의 yield point; 실행
- {value: 20, done: false} 반환
- 세 번째의 obj.next() 호출
- yield* sports(point + 10);에서 자신 호출
- 첫 번째 줄의 yield point; 실행
- {value: 30, done: false} 반환
- {주의} yield point;가 없으면
- 무한 반복을 하게 됨