Generator

CheolHyeon Park·2022년 12월 9일
0

JavaScript

목록 보기
19/23

선언

generator function을 통해 반환된 객체이다. Iterable protocol을 만족하면서 Iterator protocol을 만족하는 객체이다.

const generator = function*() {
 	yield 1; 
}
console.dir(generator()); // generator
  • Iterable protocol
    Iterable protocol은 Symbol.iterator를 키로 갖는 인자가 없고 iterator객체를 반환하는 함수여야 한다.
  • Iterator protocol
    아래의 사항들을 만족한다면 Iterator 객체이다.
    -- 인자가 없는 next() 메소드를 갖는다.
    -- next()메소드는 {value: any, done: boolean}형식의 IteratorResult객체를 반환한다.

기본적인 메소드

  • Generator.prototype.next(): generator를 재개하는 메소드이다. next()함수의 인자로 제너레이터 내부에 yield에게 값을 전달할 수 있다.
const gen = function*() {
  let i = 0;
  while(true){
    let v;
    v = yield i++;
    console.log(v)
  }
}();
console.log(gen.next(4));  // {value: 0, done: false}
console.log(gen.next(5));
// 4,
// {value: 1, done: false}
  • Generator.prototype.return(): yield 부분에 return문이 들어간 것 처럼 작동한다. try...catch문과 함께라면 제너레이터에게 제너레이터가 종료되었음을 알릴 수 있다.
const gen = function*(){
  yield 1;
  try {
    yield 2;
    yield 3;
  } finally {
    yield 'clean up';
  }
}
const gen1 = gen();
console.log(gen1.next()); // {value: 1, done: false}
console.log(gen1.return('early return')); // {value: 'early return', done: true');
console.log(gen1.next()); // {value: undefined, done: false}

const gen2 = gen();
console.log(gen2.next()); // {value: 1, done: false}
console.log(gen2.next()); // {value: 2, done: false}
console.log(gen2.return('early return')); // {value: 'clean up', done:false}
console.log(gen2.next()); // {value: 'early return', done:true}
console.log(gen2.next()); // {value: 'undefined', done:true}

try...catch블록에서 return()이 실행된다면 finally 문의 yield가 실행되고 done이 true가 된다.

  • Generator.prototype.throw(): yield된 부분에서 throw한 것처럼 작동하게 된다. try...catch문과 같이 사용한다면 catch문으로 바로 이동할 수 있다.
function* gen4() {
  while (true) {
    try {
      yield 42;
    } catch (e) {
      console.log('Error caught!');
    }
  }
}

const g = gen4();
g.next();
// { value: 42, done: false }
g.throw(new Error('Something went wrong'));
// "Error caught!"
// { value: 42, done: false }

특징

generator는 Iterable이면서 Iterator이다.

const gen = function*() {
  let i = 0;
  while(true){
    yield i++;
  }
}();

console.log(typeof gen[Symbol.iterator])
console.log(gen[Symbol.iterator]) // 함수면서 Symbol.iterator를 가지고 있다. ==> iterable protocol을 만족한다.
console.log(typeof gen.next)
console.log(gen.next()) // 함수면서 next()함수를 가지고 있다. 또한, next()함수의 결과가 Iterator Result 객체이다. ==> iterator protocol이다.
console.log(gen[Symbol.iterator]() === gen)  // true, Symbol.iterator 키가 자기 자신을 반환한다.
  • Symbol.iterator메소드를 가지고 있고, iterator객체(자기자신)를 반환한다. => Iterable protocol을 만족한다.
  • next()메소드를 갖고 Iterator Result 객체를 반환한다. => Iterator protocol을 만족한다.

두가지를 만족하는 generator는 Iterable이면서 Iterator 객체이다.

위 특성을 이용하여 직접 Generator처럼 동작하는 객체를 만들 수 있다.

const genObject = {
  [Symbol.iterator]() {
    return this;
  },
  next() {
    const v = [1,2,3];
    return {
      value: v.shift(), 
      done: v.length === 0,
    }
  }
}

console.log(typeof genObject[Symbol.iterator]) // function
console.log(typeof genObject.next) // function
console.log(genObject.next()) // {value: 1, done: false}
console.log(genObject[Symbol.iterator]() === genObject)  // true

또한, Iterable하기 때문에 spread연산자나 for...of와 같은 구문을 사용할 수 있다.

const object = function* () {
  let i = 10;
  while (i--) {
    yield i;
  }
};
for (const iterator of object()) {
  console.log(iterator);
} // 9,8,7,6,5,4,3,2,1,0
console.log(...object()); // 9 8 7 6 5 4 3 2 1 0
console.log(new Array(...object())); // [ 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 ]

suspend와 resume이 가능한 루틴이다.

suspend는 일시정지를 뜻하고 resume은 재개를 뜻한다. 일반적인 루틴의 경우 실행을 하면 루틴 내부 코드를 모두 실행하고 종료된다. 하지만 generator는 필요한 만큼만 실행할 수 있다.

const gen = function*() {
  let i = 0;
  while(true){
    yield i++;
  }
}();

console.log(gen.next().value) // 0
console.log(gen.next().value) // 1
console.log(gen.next().value) // 2
console.log(gen.next().value) // 3
console.log(gen.next().value) // 4
console.log(gen.next().value) // 5

generator gennext()를 호출하면 while문 안에 있는 yield키워드에서 정지한다. i를 1증가 시키고 빠져나온다. 다시 next()를 호출하면 i가 1인 상태로 다시 while문을 돌아 i값을 리턴하고 1을 증가 시켜 2가 된다.
이렇게 루틴의 일시정지와 재개를 자유롭게 밖의 코드에서 제어할 수 있게 된다. 이런 성질을 이용하여 무한 수열값을 출력할 수 있게 된다.
이러한 특징은 저장할 수 없는 반복문이나 조건문과 같은 문(statement)을 값 형태로 저장했다고도 볼 수 있다.

활용

  • custom Iterable을 만들 수 있다.
const cardDeck = {
  suits: ['♠️', '♣️', '♦️', '♥️'],
  court: ['J', 'Q', 'K', 'A'],
  [Symbol.iterator]: function* () {
    for (const suit of this.suits) {
      for (let i = 2; i <= 10; i++) {
        yield suit + i;
      }
      for (const c of this.court) {
        yield suit + c;
      }
    }
  },
};
const arr = [...cardDeck];
console.log(arr, arr.length); // ["♠️2", "♠️3",...] 52
  • 지연평가를 통해 무한한 시퀀스를 만들 수 있다.
function* infinity() {
  let i = 1;
  while (true) {
    yield i++;
  }
}

function* take(n, iterable) {
  for (const item of iterable) {
    if (n <= 0) return;
    n--;
    yield item;
  }
}

console.log([...take(5, infinity())]); // [1,2,3,4,5]

while 조건문을 true를 두어도 프로그램이 죽지 않는다.

  • 상태 머신으로서 작동한다.
function* bankAccount() {
  let balance = 0;
  while (balance >= 0) {
    balance += yield balance;
  }
  return 'bankrupt';
}

let acct = bankAccount();
console.log(acct.next()); // { value: 0, done: false }
console.log(acct.next(50)); // { value: 50, done: false }
console.log(acct.next(-10)); // { value: 40, done: false }
console.log(acct.next(-60)); // { value: "bankrupt", done: false }

balance 상태를 generator가 종료할 때까지 유지하는 기능이 있다.

침고

profile
나무아래에 앉아, 코딩하는 개발자가 되고 싶은 박철현 블로그입니다.

0개의 댓글