제너레이터(Generator)

JaeungE·2022년 3월 31일
0

JavaScript

목록 보기
16/16
post-thumbnail

제너레이터 란?


ES6에서 추가된 함수이며, 기존의 함수 생성과는 다르게, function 키워드 뒤에 애스터리스크(Asterisk, *)를 붙여 생성하는 함수다.

제너레이터 함수를 실행하면 제너레이터 객체를 반환하며, 반환된 제너레이터 객체는 Well-formed iterable이다.

그럼 Well-formed iterable이란 무엇일까? 아래에서 자세히 알아보자!😆



Well-formed iterable


Well-formed iterableiterator 객체이면서, 동시에 iterable 객체인 것을 말한다.

이 말은 iterable 객체가 반환하는 iterator 객체 또한, [Symbol.iterator]() 메서드를 구현한 iterable 객체라는 의미이다.

............ㅔ?

뭔가 말장난 같지만, 아래의 코드를 보면 이해할 수 있다!😃


const wfIterable = {
  [Symbol.iterator]() {
    let i = 0;

    return {
      next() {
        return { value : i++, done : false};
      },
      [Symbol.iterator]() { return this; }
    }
  }
};

iterable 객체인 wfIterable이 반환하는 iterator 객체를 보면 next() 메서드 뿐만 아니라, [Symbol.iterator]() 메서드도 구현되어 있으며 반환 값은 this인 것을 볼 수 있다.

그럼 Well-formed iterable이 아닌 것과는 무슨 차이가 있을까? 바로, Well-formed iterable은 이전 진행 상황을 기억한다는 점이다.

둘의 차이를 간단한 예제를 통해 알아보자.


Non Well-formed iterable

const nonwfIterable = {
  [Symbol.iterator]() {
    let i = 1;

    return {
      next() {
        return { value : i++, done : false};
      }
    }
  }
};

const iterator = nonwfIterable[Symbol.iterator]();

iterator.next(); // { value: 1, done: false }
iterator.next(); // { value: 2, done: false }

for (const value of nonwfIterable) {
  if (value > 5) break;

  console.log(value); // 1 2 3 4 5
}

1부터 증가하는 무한 이터레이터를 만들고, value가 5를 넘게되면 반복문을 탈출하는 예제다.

for...of 문을 통해 순회한 결과 iterator.next()를 두 번 호출했음에도, 1부터 5까지 출력하는 것을 볼 수 있다.


Well-formed iterable

const wfIterable = {
  [Symbol.iterator]() {
    let i = 1;

    return {
      next() {
        return { value : i++, done : false};
      },
      [Symbol.iterator]() {
        return this;
      }
    }
  }
};

const iterator = wfIterable[Symbol.iterator]();

console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }

for (const value of iterator) {
  if (value > 5) break;

  console.log(value); // 3 4 5
}

결과를 보면 for...of 문으로 순회하기 전에 호출했던 iterator.next()까지의 진행 상황을 기억하고, 그 이후부터 순회하는 것을 볼 수 있다.



제너레이터 순회

제너레이터 함수를 호출하여 얻은 제너레이터 객체 또한 next() 메서드를 가지고 있다.

next() 메서드를 호출하면 제너레이터 함수가 실행되어 yield 문을 만날 때까지 진행하고, yield에 명시되어있는 값을 반환한다.


const generator = function* () {
  console.log('before yield 1');
  yield 1;
  console.log('before yield 2');
  yield 2;
  console.log('before yield 3');
  yield 3;
}

const iterator = generator();

console.log(`yield 1 value : ${iterator.next().value}`);
console.log(`yield 2 value : ${iterator.next().value}`);
console.log(`yield 3 value : ${iterator.next().value}`);

// before yield 1
// yield 1 value : 1
// before yield 2
// yield 2 value : 2
// before yield 3
// yield 3 value : 3

특이한 점은, 아래처럼 next() 메서드에 인수를 전달해서 사용할 수도 있다는 점이다.


const generator = function* () {
  console.log('Hello~');
  let str = yield;
  console.log(str);

  console.log('nice to meet you');
  str = yield;
  console.log(str);
}

const iterator = generator();

iterator.next(); // Hello~
iterator.next('Hi!'); // Hi! nice to meet you
iterator.next('nice to meet you too!'); // nice to meet you too!

제너레이터는 next() 메서드에 인수를 전달해서 호출하는 경우, 진행을 멈췄던 yield문을 next() 메서드에 전달한 인자값으로 치환해서 실행한다.



제너레이터 위임(Delegation)

제너레이터는 yield* 표현식을 만나게 되면, 다른 제너레이터 함수에게 iterator의 제어권을 위임한다.


function* gen1() {
  yield 1;
  yield* gen2();
  yield 5;
}

function* gen2() {
  yield 2;
  yield 3;
  yield 4;
}

const iterator = gen1();

for (const value of iterator) {
  console.log(value); // 1 2 3 4 5
}

gen1()의 메서드는 yield*를 만나면 iterator의 제어권을 gen2()에게 위임하고, 이후 iterator에서 호출하는 next() 메서드는 gen2() 함수를 실행하게 된다.


또한 yield*는 구문이 아닌 표현이기 때문에, 아래처럼 값으로 평가된다.


function* gen1() {
  result = yield* gen2();
}

function* gen2() {
  return 'some value';
}

let result = 'default';
console.log(result); // default

const iterator = gen1();
iterator.next();
console.log(result); // some value





참고 자료

Generator | PoiemaWeb

function* | MDN

Generator | MDN

Iteration Protocols | MDN

Generator.prototype.next() | MDN

yield* | MDN

0개의 댓글