티스토리 뷰

코루틴에서 제너레이터, 이터러블/이터레이터까지 여정

1. 코루틴?

코루틴(coroutine)을 알기 위해서는 루틴(routine)에 대한 이해가 먼저 필요하다. 루틴은 우리가 잘 알고 있는 function()을 생각하면 된다.

루틴(Routine)

  • 한 번 입장하면 무조건 반환된다.
  • 반복적으로 사용할 수 있다.
  • 인자를 받아들여 내부 로직에 활용할 수 있다.

코루틴(Coroutine)

  • 여러번 진입할 수 있고 여러번 반환할 수 있다.
  • 특수한 반환을 통해 그 다음 진입을 지정할 수 있다.

특징

  • 동기명령을 일시적으로 멈춘다. -> suspend
  • 다시 진입해서 그 다음부터 실행한다. -> resume

루틴에서 명령은 적재되면 한 번에 다 실행된다. 하지만 코루틴은 중간에 멈출 수도 재개할 수도 있다. 루틴과 코루틴의 가장 큰 차이다.

2. 제너레이터

제너레이터는 이터러블을 생성하는 함수이며 실행을 중간에 멈췄다가 필요한 시점에 재개할 수 있는 기능(코루틴의 특징)을 가진다.

참고 : 제너레이터는 코루틴의 특징을 가졌기 때문에 제너레이터 = 코루틴이라고 생각할 수 있다. 하지만 제너레이터는 코루틴의 일종이지 코루틴은 아니다. 코루틴은 반환시 다음 진입점을 지정할 수 있어야 하는데 제너레이터는 다음 yield로 이동하지, 진입지점을 맘대로 정할 수는 없기 때문이다. 그래서 제너레이터는 세미 코루틴이라고 한다. 자세한 설명은 위키이 글에 정리가 잘 되어 있다.

사용법

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

const g = gen(); // 제너레이터 객체가 반환된다.
  • 제너레이터 함수는 function* 키워드로 선언한다.
  • 하나 이상의 yield문을 포함한다.
    • yield에서 메서드의 실행을 멈출 수 있다.
  • 제너레이터 함수를 호출하면 제너레이터 객체가 반환된다.
g.next(); // {value: 1, done:false}
g.next(); // {value: 2, done:false}
g.next(); // {value: 3, done:false}
g.next(); // {value: undefined, done:true}
  • 제너레이터 객체의 next()를 호출하면 처음 만나는 yield문까지 실행되고 일시 중단(suspend)된다.
  • 또다시 next()를 호출하면 중단된 위치에서 다시 실행(resume)되어 다음에 만나는 yield문까지 실행되고 일시 중단(suspend)된다.

활용 - 무한 이터러블

const infinity = (functon*() {
    let i = 0;
    while(true) yield i++;
})();
console.log(infinity.next());
console.log(infinity.next());

피보나치

function* fibonacci() {
  let current = 0;
  let next = 1;
  while (true) {
    let reset = yield current;
    [current, next] = [next, next + current];
    if (reset) {
        current = 0;
        next = 1;
    }
  }
}

const sequence = fibonacci();
console.log(sequence.next().value);     // 0
console.log(sequence.next().value);     // 1
console.log(sequence.next().value);     // 1
console.log(sequence.next().value);     // 2
console.log(sequence.next().value);     // 3
console.log(sequence.next().value);     // 5
console.log(sequence.next().value);     // 8
console.log(sequence.next(true).value); // 0
console.log(sequence.next().value);     // 1
console.log(sequence.next().value);     // 1
console.log(sequence.next().value);     // 2

3. 이터러블과 이터레이터

Generator는 iterable이면서 동시에 iterator인 객체다. 그래서 Symbol.iterator 메서드로 이터레이터를 별도로 생성할 필요가 없다.

Iterable

  • iterable protocol의 구현체
  • iterable은 iteration의 행동을 정의한다.
  • Symbol.iterator 메서드가 있다.
  • Symbol.iterator는 iterator를 반환해야 한다.

Iterator

  • iterator protocol의 구현체
  • next() 메서드를 가진다.
  • next()는 이터러블을 순회하며 valuedone 속성을 가진 객체를 반환한다.
  • 작업이 끝나면 done은 true가 된다.

Iteration protocol

데이터 컬렉션을 순회하기 위한 프로토콜이다. 이터레이션 프로토콜에는 이터러블 프로토콜(iterable protocol)이터레이터 프로토콜(iterator protocol)이 있다.

  1. 이터레이션 프로토콜을 준수한다는 것은 Symbol.iterator 메서드를 가지고 있다는 것이다.
  2. Symbol.iterator 메서드는 iterator를 반환한다. (이터러블 프로토콜)
  3. iteratornext 메서드로 iterable을 순회하며 valuedone 속성을 가진 객체를 반환한다. (이터레이터 프로토콜)
const gen = {
    // iterable
    [Symbol.iterator]() {
        const arr = [1,2,3,4];
        let cursor = 0;
        return {
            // iterator
            next() {
                return {done:cursor >= arr.length, value:cursor < arr.length ? arr[cursor++] : undefined };
            }
        };
    }
};

const iter1 = gen[Symbol.iterator]();
const iter2 = gen[Symbol.iterator]();

위 코드에서 구현한 gen은 이터러블이면서 이터레이터인 객체다.

참고자료

댓글