2 분 소요

지난 포스팅에서 이터러블과 이터레이터에 대해서 살펴보았다.

또한 제너레이터라는 것을 사용하면 이터레이터를 편리하게 만들 수 있다는 것을 알게 되었다. 이번 포스팅에서는 제너레이터를 사용하여 이터레이터를 만들어보자.


▶ 제너레이터

  • 제너레이터는 함수의 실행을 중간에 멈췄다가 재개할 수 있는 기능을 가진 함수를 말한다.
  • 반복할 수 있는 이터레이터 객체를 생성한다.

  • *가 붙은 함수가 제너레이터 함수이다.
  • 제너레이터 함수는 실행하면, Iterator 객체가 반환(next()를 가지고 있음)된다.
  • iterator 은 객체는 next 메서드로 순환 할 수 있는 객체이며, 비동기 작업이 완료되는 시점마다 next 메서드를 호출해주면 Generator 함수 내부소스가 위 -> 아래로 순차적으로 진행된다.


▷ 일반함수 vs 제너레이터

제너레이터가 일반 함수와 다른 점은 함수의 실행을 제어할 수 있다는 점이다.

  • 단 한 번의 실행으로 함수의 끝까지 실행이 완료되는 일반 함수와는 달리, 제너레이터 함수는 사용자의 요구에 따라 (yield와 next를 통해) 일시적으로 정지될 수도 있고, 다시 시작될 수도 있다.
  • 일반 함수는 호출이 되면 함수의 코드 블록을 실행시키지만, 제너레이터 함수는 제너레이터 객체를 생성해서 반환한다.
  • 일반 함수는 하나의 값(혹은 0개의 값)만 반환하지만 제너레이터를 사용하면 여러 개의 값을 필요에 따라 하나씩 반환(yield)할 수 있다.

그렇다면 제너레이터 객체란 무엇일까?


▷ 제너레이터 객체와 이터러블

제너레이터 함수를 호출했을 때 리턴되는 객체를 제너레이터 객체라고 한다. 여기서 객체는 이터러블이면서 동시에 이터레이터이다.

function* createIterator() {
  yield 1;
  yield 2;
  yield 3;
}
// generator는 iterator를 반환한다.
let iterator = createIterator();
console.log(iterator.next().value); // 1
console.log(iterator.next().value); // 2
console.log(iterator.next().value); // 3

따라서 for-of 반복문을 사용해 값을 얻을 수 있다.

function* generateSequence() {
  yield 1;
  yield 2;
  return 3;
}

let generator = generateSequence();

for (let value of generator) {
  alert(value); // 1, 2
}


⚠️ 위 코드에서 3은 출력되지 않는다. 왜 그럴까?

for-of 이터레이션이 done: true일 때 마지막 value를 무시하기 때문이다. 그러므로 for-of를 사용했을 때 모든 값이 출력되길 원한다면 yield로 값을 반환해야 한다.


function* generateSequence() {
  yield 1;
  yield 2;
  yield 3;
}

let generator = generateSequence();

for (let value of generator) {
  alert(value); // 1, 2, 3
}


▷ 이터레이터 구현

제너레이터를 사용하지 않고 이터레이터(객체의 반복)를 구현해보자

function createIterator(items) {
  let i = 0;
  return {
    next: function () {
      const done = i >= items.length;
      const value = !done ? items[i++] : undefined;
      return {
        done: done,
        value: value,
      };
    },
  };
}

const iterator = createIterator([1, 2, 3]);
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: 3, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"

제너레이터를 사용하여 이터레이터(객체의 반복)를 구현해보자.

제너레이터 문법

  • 제너레이터를 만들려면 제너레이터 함수라 불리는 function*이 필요하다.
    • function 다음에 붙은 별표* (= 에스터리스크)는 이 함수가 제너레이터 함수라는 의미이다.
  • 제너레이터 함수는 yield 키워드를 만나면 해당 지점에서 동작을 멈추며 yield 키워드 뒤에 지정된 값을 반환한다.
  • 제너레이터는 이터러블이므로 next()함수를 사용하여 제너레이터 함수를 호출할 수 있다.
function* createIterator() {
  yield 1;
  yield 2;
  yield 3;
}

// generator는 iterator를 반환한다.
let iterator = createIterator();
console.log(iterator.next().value); // 1
console.log(iterator.next().value); // 2
console.log(iterator.next().value); // 3

확실히 제너레이터를 사용한 쪽이 코드의 가독성, 간결성면에서 우수하다고 할 수 있다.


  • 또한 제너레이터 함수는 본문에 return문을 작성하여 함수의 동작을 끝낼 수 있다.
function* generatorFunc(n) {
  yield n;
  return n + 1; // 제너레이터 종료
  yield n + 2; // 동작 안함
}

const generatorObj = generatorFunc(1);

console.log(generatorObj.next());
console.log(generatorObj.next());
console.log(generatorObj.next());


실제 예제

// (1) 제너레이터 함수 안에서 쓸 addCoffee 함수 선언
var addCoffee = function (prevName, name) {
  setTimeout(function () {
    coffeeMaker.next(prevName ? prevName + ", " + name : name);
  }, 500);
};
// (2) 제너레이터 함수 선언
// yield 키워드로 순서 제어
var coffeeGenerator = function* () {
  var espresso = yield addCoffee("", "에스프레소");
  console.log(espresso);
  var americano = yield addCoffee(espresso, "아메리카노");
  console.log(americano);
  var mocha = yield addCoffee(americano, "카페모카");
  console.log(mocha);
  var latte = yield addCoffee(mocha, "카페라떼");
  console.log(latte);
};

// coffeeMaker는 이터레이터 객체
var coffeeMaker = coffeeGenerator();
coffeeMaker.next();




📎참조

  • https://velog.io/@ggong/ES6-%EC%9D%B4%ED%84%B0%EB%A0%88%EC%9D%B4%ED%84%B0%EC%99%80-%EC%A0%9C%EB%84%88%EB%A0%88%EC%9D%B4%ED%84%B0
  • https://developer-talk.tistory.com/274
  • https://ko.javascript.info/generators
  • https://seo-tory.tistory.com/77

댓글남기기