article thumbnail image
Published 2020. 11. 23. 17:22

JavaScript에서 iterator 객체를 쉽게 구현할 수 있는 방법에는 Generator가 있습니다.

Generator 란?

Generators are functions that can be exited and later re-entered. Their context (variable bindings) will be saved across re-entrances. - MDN.

Generator 의 특징

generator의 특징은 함수가 특정 지점에서 끝나고 다음 실행 때는 끝난 시점에서 다시 시작된다는 것입니다.

function* <함수명>(<파라미터>) {
  // syntax;
  }

위처럼 *를 붙이는 함수는 generator가 됩니다. 이 함수는 특별한 함수인데 일반적인 함수와는 다르게 내부에서 yield라는 문법이 사용가능합니다.

 

yield : return 처럼 함수를 종료합니다. 다만 함수를 재호출(next( ))할 경우 해당 지점에서 다시 시작됩니다.

 

function* generatorFunc() {
  console.log('first call');
  yield 1;
  console.log('second call');
  yield 2;
  console.log('third call');
  yield 3;
}

let generator = generatorFunc();
console.log(generator.next());
console.log(generator.next());
console.log(generator.next());
console.log(generator.next());

위의 코드에서 어떤 일이 일어났길래 콘솔 창에 다음과 같이 출력된 것일까?

1. let generator = generatorFunc( ); 에서,

generatorFunc이 호출되는데, 이는 일반적인 함수의 호출과는 좀 다르게 동작합니다. 즉시 함수 내부의 코드를 실행하지 않고, 함수 내부의 코드들을 실행할 수 있도록 준비 상태로 만들어줍니다. iterator를 지정한다고 생각하면 될 것 같습니다.

그 다음으로 next( )를 호출하면 generatorFunc 내부의 코드들이 실행됩니다. next 메서드는 어떤 동작을 하고 있을까요?

 

next( ) : generator를 실행합니다. yield 구문까지 실행하고 종료합니다. 또한 재호출 시 마지막 yield 지점에서 시작합니다.

 

2. generator.next( ) 호출이 차례대로 4번 일어나게 됩니다.

첫 호출에서 first call이 출력되고 그 다음 yield 1에서 종료됩니다. 그리고 1을 반환값으로 반환하는데, 이때 generator는 값을 object로 감싸서 반환합니다. 세 번째 호출 때까지도 이와 동일하게 동작합니다. 하지만 네 번째 호출에서 우리는 이미 마지막 yield 구문까지 실행되었기 때문에 done은 true가 되고, yield에 의해 반환되는 값이 없습니다.

 

value는 yield에 의해 반환된 값이고 done은 generator가 마지막 yield 구문까지 실행했느냐를 의미합니다.

마지막 yield 구문을 실행했다면 done은 true가 됩니다.  

 

Generator 객체는 iterator 인가 아니면 iterable 인가?

결론부터 말하자면, generator 객체는 iterator 이며 동시에 iterable 이다.

 

let generatorObject = function* () {
  yield 1;
  yield 2;
  yield 3;
}();

console.log(typeof generatorObject.next);
// "function", generatorObject는 next 메서드를 가지고 있기 때문에 iterator이다.

console.log(typeof generatorObject[Symbol.iterator]);
// "function", generatorObject는 @@iterator 메서드를 가지고 있기 때문에 iterable이다.

console.log(generatorObject[Symbol.iterator]() === generatorObject);
// true, @@iterator 메서드는 iterator를 반환하며 generatorObject는 iterator이기 때문이다.

console.log([...generatorObject]);
// [1, 2, 3]

console.log(Symbol.iterator in generatorObject);
// true, @@iterator 메서드는 generatorObject의 프로퍼티이기 때문이다.

 

Generator 의 예시

1. Generating an infinite number of unique identifiers

function* idMaker() {
  let index = 0;
  while (true) {
    index++;
  }
}

let gen = idMaker();

console.log(gen.next().value); // '0'
console.log(gen.next().value); // '1'
console.log(gen.next().value); // '2'
// ...

 

2. 사용자들이 3주의 운동 사이클을 계산할 수 있는 어플리케이션을 만들려고 합니다. 한 사이클은 1주일에 3 ~ 7일 운동을 한다고 가정합니다. 운동은 크게 squat, bench press, deadlift, overhead press로 나뉘고 반드시 이 순서대로 계속 운동한다고 가정합니다.

 

without generator:

const lifts = ['squats', 'bench', 'deadlift', 'press'];
const numWeeks = 3;
const daysPerWeek = 6;

const totalNumSessions = numWeeks * daysPerWeek;

let currentLiftIndex = 0;

const cycle = [...Array(totalNumSessions)].map(() => ({
  lift: lifts[currentLiftIndex++ % lifts.length]
}));

lift 프로퍼티의 값에서 연산이 이루어지는 동시에 값이 저장되기 때문에 다소 가독성이 떨어집니다.

 

with generator:

function* repeatedArray(arr) {
  let index = 0;
  while (true) {
    yield arr[index++ % arr.length]
  }
}

const lifts = ['squat', 'bench', 'deadlift', 'press'];
// repeatedArray returns an iterator object for the repeatedArray function itself
// iterator is stored in a variable named nextLiftGenerator
// the code in the function hasn't been executed at this point.
const nextLiftGenerator = repeatedArray(lifts);

const numWeeks = 3;
const daysPerWeek = 6;

const totalNumSessions = numWeeks * daysPerWeek;

const cycle = [...Array(totalNumSessions)].map(() => ({
  lift: nextLiftGenerator().next().value,
}));

generator가 사용된 코드는 좀 더 선언적이고 가독성이 좋아졌습니다.

lifts 배열 안에서 index를 추적하는 로직은 repeatedArray 함수로 추상화하였고, function* 은 generator 함수를 의미합니다.

이제 남은 일은 repeated array에서 다음 item 을 요청하는 것입니다. generator 가 이를 제공해줍니다.

generator 함수를 사용하여 무제한으로 반복되는 배열을 만들어서 연산을 수행하였습니다. 

 

출처

MDN - Iteration protocols

ITNEXT - A Quick, Pratical Use Case for ES6 Generators

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

복사했습니다!