제너레이터(Generator)

Winney·2021년 3월 7일
0
post-thumbnail

❓ redux-saga를 익히고 있는 도중 generator에 대해서 반드시 이해하고 넘어가야 할 것같아서 공부하고 있다.
블로그에 간단히 정리하며 마무리 지을 생각이다.

Generator란?

비동기 작업을 동기적으로 표현하기 위한 방법 중 하나이다.
그 외 Promise, async/await가 있다.

1. 제너레이터는 어떻게 작성할까?

  • function*과 같이 *function 뒤에 붙이면 제너레이터가 된다.
  • 제너레이터 내부에 yield를 입력하면 제너레이터를 중지 및 재개 할 수 있다.
  • next를 통해 제너레이터를 실행할 수 있다.
function* sayHello() {
  console.log('제너레이터 시작')
  yield '안녕';
  console.log('첫번째 yield의 비동기 작업 끝낸 후 실행');
  yield '나는 Winney야'
  console.log('두번째 yield의 비동기 작업 끝낸 후 실행');
  yield '앞으로 잘 지내자'
  console.log('제너레이터 끝');
};

const gen = sayHello();

gen.next(); // 1번
gen.next(); // 2번
gen.next(); // 3번
gen.next(); // 4번

2. 제너레이터의 실행은 어떻게 될까?

작성 방법을 알았으니 제너레이터가 어떻게 실행되는지 파악해야 할 차례이다.
다음은 위의 예시를 실행한 결과이다.

// 1번 gen.next() 실행
'제너레이터 시작'
{ value: '안녕', done: false }

// 2번 gen.next() 실행
'첫번째 yield의 비동기 작업(안녕) 끝낸 후 실행'
{ value: '나는 Winney야', done: false }

// 3번 gen.next() 실행
'두번째 yield의 비동기 작업(나는 Winney야) 끝낸 후 실행'
{ value: '앞으로 잘 지내자', done: false }

// 4번 gen.next() 실행
'제너레이터 끝'
{ value: undefined, done: true }

여기서 중요한 부분은 next를 이용해서 제너레이터를 작성했을 때 해당 제너레이터의 실행은 yield작성한 부분에서 멈춘다는 것이다.

이후 다시 next로 제너레이터를 실행했을 때 이전 yield의 다음 줄에서부터 실행되어 그 다음 `yield에서 제너레이터가 멈추는 것은 반복한다는 것이다.

즉, 위에서 말한 yield를 입력하면 제너레이터를 중지 및 재개 할 수 있다.는 의미의 yield는 코드가 어딘가에서 멈춰서 비동기작업을 하고 해당 작업이 끝나면 다시 제너레이터가 실행 될 수 있게 하는 키워드라는 의미이다.

그리고 위의 실행 결과를 통해 yield는 객체형태로 valuedone을 반환하는 것을 알 수 있다.

  • value : yield실행 결과의 반환 값
  • done: 제너레이터가 완전히 종료되었는지를 확인하는 값

때문에 4번째 gen.next()yield 반환값을 확인하면 { value: undefined, done: true }를 확인 할 수 있다.

이전 3번째 yield가 마지막으로 실행되며 멈춘 다음 줄에서 4번째 gen.next()가 실행되므로 console.log('제너레이터 끝');가 실행된 후 제너레이터가 끝난다.
그렇기에 yield가 없어서 반환값이 없으므로 valueundefined가 나오고 제너레이터는 완전히 종료되었으므로 donetrue가 된다.

3. 제너레이터로 어떤 것들을 표현 할 수 있을까?

제너레이터는 기본적으로 비동기 작업을 동기적으로 표현하므로서 가독성있게 표현하기 위한 것이다. (하지만 난 아무리 봐도 동기적으로 보인다... 물론 비동기 작업이 있다는 것을 알지만.. 😂)

제너레이터로 표현 할 수 있는 것

  • API 호출 : 대표적인 비동기 작업!
  • 자연수 표현하기
    : 개인적으로 자연수 표현한 제너레이터를 보고 나도 모르게 내가 제너레이터를 함수처럼 생각하고 있었던 문제를 알 수있었다!
function* countNaturalNum() {
  let num = 0;
  while(true) {
    num += 1
    yield num;
  }
}

const gen = countNaturalNum()
gen.next(); // { value: 1, done: false }
gen.next(); // { value: 2, done: false }
gen.next(); // { value: 3, done: false }
gen.next(); // { value: 4, done: false }

만약에 함수였다면 무한루프를 돌았을 것이지만 제너레이터를 사용하면서 자연수를 표현할 수 있게 되었다.

4. next에 인자를 넣으면 어떻게 될까?

예시와 결과가 나오는데 예시만 보고 꼭 결과를 예측해볼 것

  • next의 문법 : gen.next(value)
  • next에는 매개변수가 있고 value는 제너레이터에 전달된다.

그리고 이미 배웠다시피 nextvaluedone 프로퍼티를 가진 객체를 반환한다.

1번 예시

function* getArg() {
  while(true) {
    let value = yield null;
    console.log(value);
  }
}

const gen = getArg()
gen.next(1); // 1번
gen.next(2); // 2번

1번 예시의 결과는 어떻게 나올까?

// 1번
{ value: null, done: false }

// 2번
2
{ value: null, done: false }
  • 1번의 경우 : yield null에서 멈추었기 때문에 yield의 결과인 nullvalue: null로 나타났다.
  • 2번의 경우 : 첫번째 yield 다음 줄부터 실행되기 때문에 console.log(value)가 실행된다. 당연히 next(2)에서 2가 인자로 들어갔기 때문에 2가 출력되고 while반복문이기에 다시 돌아가서 yield null이 실행되어 value: null가 나타난다.

2번 예시

function* getArg() {
  returnedYield = yield 'foo';
  yield returnedYield;
}

const gen = getArg()
gen.next(1); // 1번
gen.next(2); // 2번

2번 예시의 결과는 어떻게 나올까?

// 1번
{ value: 'foo', done: false }

// 2번
{ value: 2, done: false }

제너레이터를 익히며 제일 어려웠던 부분이다!!

💥 중요
분명 gen.next(1)에서 1을 전달했는데 왜 value'foo'로 나올까?
returnedYield는 어디로 갔나?

  • mdn yield 문법 : [rv] = yield [expression];
    여기서 중요한 부분은 [expression]이다. 우리가 알아야 할 것은 next(value)에서 저 value 매개변수가 yield에게 전달되고 그 자리가 [expression] 자리라는 것이다. (yield오른쪽)
    때문에 gen.next(1);를 통해서 1yield 'foo'에 전달하므로서 yield 1되었다.
    하지만 yield 1returnedYield에 전달되는 것은 다음 yield가 실행될 때이다.
    그렇기에 1번 결과에서 우리는 value: 'foo'를 확인 할 수 있고 2번결과에서는 이미 next(2)를 통해 yield2value로 가진 상황에서 returnedYield를 반환하게 되므로 { value: 2, done: false }과 같은 결과가 나온 것이다.

참조 StackOverFlow : https://stackoverflow.com/questions/37354461/how-does-generator-next-processes-its-parameter

개인적으로 next에 인자를 전달한 값을 예측하는게 제일 어려운 부분이었다.
iteratoriterable에 관해서도 작성하고 싶지만 다음에 전반적으로 다루어 보도록 하고 제너레이터에 관한 내용을 마무리한다,

profile
프론트엔드 엔지니어

0개의 댓글