All Articles

자바스크립트 제네레이터

Generator

제네레이터의 개념에 대해 이해하기 전에, 먼저 반복자 (Iterator)에 대해 알아보자.

0. Iterator

반복자는, 두개의 속성 (valuedone)을 반환하는 next()메소드를 사용하여 Iterator protocal을 구현한다. 말이 조금 어려운 것 같으니, 조금 쉽게 설명해보자.

예를 들어 for ... of 구문에서 자바스크립트 객체들이 loop되는 것과 같은 iteration 동작을 정의하는 것을 허락하는 것이다. ArrayMap의 경우에는 default iteration 동작이 담겨져 있다.

더 쉽게 이야기 하자면, object가 Symbol.iterator 키 속성을 가지고 있다는 것을 의미한다. 어떤 객체가 반복가능하다면 이 메소드 @@iterator가 인수없이 호출이 가능하고, 반환된 iterator는 반복을 통해서 획득한 값을 얻을 때 사용할 수 있다.

const hello = ['hello', 'hi']
console.log(hello[Symbol.iterator]) //[Function: values], iterable

const hi = 1
console.log(hi[Symbol.iterator]) // undefined, not iterable

iterable 프로토콜이 만약 next() 메소드를 가지고 있고, 다음과 같은 규칙을 따르고 있다면 iterator라고 정의 한다.

  • next: 아래 두개의 속성을 가진 object를 반환하는 인수가 없는 함수:

    • done: (boolean) 작업을 마쳤을 경우 true 그렇지 않다면 false
    • value: (any) iterator로 부터 반환되는 모든 자바스크립트 값이며, donetrue이면 생략 가능하다.

아래의 예시를 살펴보자.

const hello = 'hello'
const stringIterator = hello[Symbol.iterator]()

console.log(stringIterator.next()) //{ value: 'h', done: false }
console.log(stringIterator.next()) //{ value: 'e', done: false } 
console.log(stringIterator.next()) //{ value: 'l', done: false }
console.log(stringIterator.next()) //{ value: 'l', done: false }
console.log(stringIterator.next()) //{ value: 'l', done: false }
console.log(stringIterator.next()) //{ value: undefined, done: true }

또 이를 활용해서 원하는 iterator를 정의할 수도 있다.

var countDown = new Number(5)

countDown[Symbol.iterator] = function() {
  var _count = 0
  var value = +this
  return {
    next() {
      _count++      
      if (_count < value) {        
        return {value: _count, done: false}
      } else {
        return {done: true}
      }
    }
  }
}

for (let i of countDown) {
  console.log(i) // 1, 2, 3, 4
}

console.log([...countDown]) // [ 1, 2, 3, 4 ]

1. Genenrator

Generator는, 하나의 값을 리턴하는 일반적인 함수와는 다르게 결과의 순서를 생성해 내는 함수라고 볼 수 있다. 앞서 설명한 itertor와 동일하게, next()를 호출하면, {value: any, done: boolean}을 리턴한다.

function * generator() {
  yield 'hello'
  yield 'hi'
}

for (let i of generator()) {
  console.log(i) // hello, hi
}
  • yield: 제네레이터 함수의 실행을 일시적으로 중지 시키며, 뒤에 오는 표현식을 반환한다. 즉 일반적인 함수의 return문 역할을 하면 된다고 본다.
  • return: 수행하고 있는 iterator를 종료시키며, return뒤의 표현식은 {value: return, done: true} 형태로 반환된다.

Generator의 형태가 Iterator와 비슷한 걸로 보았을때, Generatoriterable하다고 볼 수 있다.

한가지 알아 두어야 할 것은, next()yield가 서로 값을 주고 받을 수 있다는 점이다.

function *myGen() {
  const x = yield 1;       // x = 10
  const y = yield (x + 1); // y = 20
  const z = yield (y + 2); // z = 30
  return x + y + z;
}

const myItr = myGen();
console.log(myItr.next());   // {value:1, done:false}
console.log(myItr.next(10)); // {value:11, done:false}
console.log(myItr.next(20)); // {value:22, done:false}
console.log(myItr.next(30)); // {value:60, done: true}