제너레이터

김수정·2020년 5월 12일
0
post-custom-banner

제너레이터 함수란?

여러 개의 값을 필요에 따라 하나씩 반환할 수 있는 함수입니다.
제너레이터 함수는 function*을 통해 만들 수 있고,
함수를 호출할 때 코드가 실행되는 것이 아니라 실행을 처리하는 특별 객체 '제너레이터 객체'가 반환됩니다.

function* generateSequence() {
  yield 1;
  yield 2;
  return 3;
}

next
제너레이터의 주요 메서드로써 호출 시 가장 가까운 yield <value>문을 만날 때까지 실행합니다.
value가 없으면 undefined를 반환하며 아래와 같은 구조로 되어 있습니다.
계속 넥스트 메서드를 사용하다가 리턴문을 만나면 끝나거나, yield를 다 돌고나서 마지막에 끝납니다.

{value: 1, done: false}

이터러블

(1) 제너레이터는 이터러블이므로 for..of문을 사용할 수 있습니다.
주의할 점은 제너레이터 객체의 done이 true이면 for..of문은 무시하므로 return으로 값이 끝나지 않고 yield로 처리해야 합니다.

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

let generator = generateSequence();

for(let value of generator) {
  alert(value); // 1, 2, 3
}

(2) 전개문법 사용이 가능합니다.

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

let sequence = [0, ...generateSequence()];

alert(sequence); // 0, 1, 2, 3

제너레이터 컴포지션

제너레이션 함수 안에 다른 제너레이션 함수를 중복해서 사용할 수 있습니다.
yield* 지시자는 실행을 다른 제너레이터에 위임해 줄 수 잇습니다.

function* generateSequence(start, end) {
  for (let i = start; i <= end; i++) yield i;
}

function* generatePasswordCodes() {

  // 0..9
  yield* generateSequence(48, 57);

  // A..Z
  yield* generateSequence(65, 90);

  // a..z
  yield* generateSequence(97, 122);

}

let str = '';

for(let code of generatePasswordCodes()) {
  str += String.fromCharCode(code);
}

alert(str); // 0..9A..Za..z

next/yield를 통해 정보 교환하기

제너레이터 함수는 값을 안과 밖으로 전달할 수 있습니다.

function* gen() {
  let ask1 = yield "2 + 2 = ?";
  alert(ask1); // 4

  let ask2 = yield "3 * 3 = ?"
  alert(ask2); // 9
}

let generator = gen();

alert( generator.next().value ); // "2 + 2 = ?"
alert( generator.next(4).value ); // "3 * 3 = ?"
alert( generator.next(9).done ); // true

next로 처음 호출할 때만 값을 넣지 않고, (넣어도 무시될 겁니다) 이후에는 값을 넣어서 yield에 값을 줄 수 있습니다.
이와 같은 현상으로 에러도 던질 수 있습니다.

// (1)
function* gen() {
  try {
    let result = yield "2 + 2 = ?"; // (1)

    alert("위에서 에러가 던져졌기 때문에 실행 흐름은 여기까지 다다르지 못합니다.");
  } catch(e) {
    alert(e); // 에러 출력
  }
}

let generator = gen();
let question = generator.next().value;

generator.throw(new Error("데이터베이스에서 답을 찾지 못했습니다.")); // (2)

// (2)
function* generate() {
  let result = yield "2 + 2 = ?"; // Error in this line
}

let generator = generate();
let question = generator.next().value;

try {
  generator.throw(new Error("데이터베이스에서 답을 찾지 못했습니다."));
} catch(e) {
  alert(e); // 에러 출력
}

async 제너레이터

비동기로 제너레이터를 사용하는 방법에 대해 알아봅니다.

async iterator

비동기 이터레이터는 아래의 특성을 갖춰야 합니다.

  • Symbol.asyncIterator 속성을 사용합니다.
  • next()가 프라미스를 반환해야 합니다.
  • 비동기 이터러블 객체를 대상으로 하는 반복 작업은 for await..of문을 사용합니다.
let range = {
  from: 1,
  to: 5,

  // for await..of 최초 실행 시, Symbol.asyncIterator가 호출됩니다.
  [Symbol.asyncIterator]() { // (1)
    // Symbol.asyncIterator 메서드는 이터레이터 객체를 반환합니다.
    // 이후 for await..of는 반환된 이터레이터 객체만을 대상으로 동작하는데,
    // 다음 값은 next()에서 정해집니다.
    return {
      current: this.from,
      last: this.to,

      // for await..of 반복문에 의해 각 이터레이션마다 next()가 호출됩니다.
      async next() { // (2)
        //  next()는 객체 형태의 값, {done:.., value :...}를 반환합니다.
        // (객체는 async에 의해 자동으로 프라미스로 감싸집니다.)

        // 비동기로 무언가를 하기 위해 await를 사용할 수 있습니다.
        await new Promise(resolve => setTimeout(resolve, 1000)); // (3)

        if (this.current <= this.last) {
          return { done: false, value: this.current++ };
        } else {
          return { done: true };
        }
      }
    };
  }
};

(async () => {

  for await (let value of range) { // (4)
    alert(value); // 1,2,3,4,5
  }

})()

async generator

async function* generateSequence(start, end) {

  for (let i = start; i <= end; i++) {
    // await를 사용할 수 있습니다!
    await new Promise(resolve => setTimeout(resolve, 1000));
    yield i;
  }

}

(async () => {

  let generator = generateSequence(1, 5);
  for await (let value of generator) {
    alert(value); // 1, 2, 3, 4, 5
  }

})();
// async generator의 next 호출
result = await generator.next(); // result = {value: ..., done: true/false}

async iterable

Symbol.asyncIterator를 generator로 구현하기

let range = {
  from: 1,
  to: 5,

  async *[Symbol.asyncIterator]() { // [Symbol.asyncIterator]: async function*()와 동일
    for(let value = this.from; value <= this.to; value++) {

      // 값 사이 사이에 약간의 공백을 줌
      await new Promise(resolve => setTimeout(resolve, 1000));

      yield value;
    }
  }
};

(async () => {

  for await (let value of range) {
    alert(value); // 1, 2, 3, 4, 5
  }

})();
profile
정리하는 개발자
post-custom-banner

0개의 댓글