[자바스크립트 ES6+ 기본] 17. Generator 오브젝트

Speedwell🍀·2022년 2월 3일
0

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}
  1. create = (function*(){}).constructor;

    • 제너레이터 함수를 생성하는 constructor(생성자)를 할당
  2. constructor가 할당되므로

    • new 연산자로 생성자 함수를 호출할 수 있음
  1. log(create);

    • function GeneratorFunction(){} 출력
    • function 오브젝트 형태
  2. sports = new create(param)

    • GeneratorFunction을 사용하여 제너레이터 함수를 생성하고 sports 변수에 할당
    • param에 파라미터와 함수 코드를 작성
      • one: 파라미터 이름
      • yield one: 함수 코드
  3. log(typeof sports)

    • new 연산자를 사용했는데
    • sports가 object가 아닌 function
  4. function이라는 것은

    • function* sports()로 제너레이터 함수를 선언한 것을 뜻함
    • 즉, 지금까지 제너레이터 함수를 선언하는 처리를 한 것
  5. const obj = sports(100);

    • 제너레이터 함수를 호출
    • 제너레이터 오브젝트 생성, 반환
    • 함수 코드를 실행하지 않음
    • 100이 one에 매핑됨
  6. 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}
  1. function* sports(one){}

    • 제너레이터 함수 선언
    • 3개의 yield 작성함
  2. const obj = sports(10);

    • 제너레이터 오브젝트 생성
    • 파라미터 값, 10이 one에 설정됨
  3. 첫 번째의 obj.next()를 호출

    • let two = yield one이 실행됨
    • one의 값인 10을 반환
    • two 변수에 10을 할당하지 않음
  4. 두 번째의 obj.next()를 호출

    • next()에 파라미터 값을 작성하지 않았으므로 two 변수에 undefined가 설정됨
    • let param = yield one + two를 실행
    • two 변수 값이 undefined이므로 NaN 반환
  5. 세 번째의 obj.next(20)를 호출

    • 파라미터 값 20이 바로 앞의 param 변수에 설정됨
    • yield param + one 실행
    • 20 + 10 반환
  6. 네 번째의 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}
  1. 첫 번째의 obj.next()를 호출하면

    • yield* point(value); 실행
  2. yield*의 표현식에 함수를 작성했으므로

    • point(value) 호출
    • point()가 제너레이터 함수이므로
    • 우선, 제너레이터 오브젝트 생성
  3. next()로 호출해야 yield가 수행되지만

    • 자동으로 point() 첫 번째의 yield count + 5;를 수행
    • {value: 15, done: false} 반환
  4. 다시 point()를 호출한 곳에서 반환 값을 받아 반환

  5. 두 번째의 obj.next()를 호출

    • point()의 yield count + 10;를 실행
    • {value: 20, done: false} 반환
  6. 세 번째의 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}
  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() 호출

    • yield* sports(point + 10);에서 자신 호출
    • 첫 번째 줄의 yield point; 실행
    • {value: 30, done: false} 반환
  4. {주의} yield point;가 없으면

    • 무한 반복을 하게 됨

0개의 댓글