yceffort

자바스크립트 제네레이터

Published on May 20, 2020

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}