제너레이터(Generator)

박재현·2022년 4월 11일
0

FE 톺아보기

목록 보기
4/10
post-thumbnail

제너레이터란?

일반 함수는? 하나의 값 만을 반환한다.

  • 제너레이터를 사용하면 여러 개의 값을 필요에 따라 하나씩 반환할 수 있다.
  • 제너레이터와 이터러블 객체를 함께 사용하면 손쉽게 데이터 흐름 집합체를 만들 수 있다.

제너레이터 함수

function*

제너레이터 함수를 호출하면 코드가 실행되지 않고, 제너레이터 객체가 반환된다.

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

let generator = generatorFunc();
console.log(generator); // Object [Generator] {}

next()

  • 제너레이터 주요 메서드
  • 가장 가까운 yield <value> 문을 만날 때까지 실행이 지속
  • yield <value> 문을 만나면 실행이 멈추고 산출하고자 하는 값인 value 가 바깥 코드에 반환
  • 두 프로퍼티를 가진 객체를 반환
    • value: 산출 값
    • done: 함수 코드 실행이 끝났으면 true, 아니라면 false
  • next() 메서드를 입력할 때마다 다음 가장 가까운 yield <value> 문을 만날 때까지 실행이 지속
function* generatorFunc() {
  yield 1;
  yield 2;
  return 3;
}

let generator = generatorFunc();
let one = generator.next();
console.log(one); // { value: 1, done: false }
let two = generator.next();
console.log(two); // { value: 2, done: false }
let three = generator.next();
console.log(three); // { value: 3, done: true }
let four = generator.next();
console.log(four); // { value: undefined, done: true }

제너레이터와 이터러블

  • 제너레이터는 iterable

  • for...of 반복문을 사용해 값을 얻을 수 있음

  • return 값을 반환할 수 없음 return 3 → yield 3

  • 제너레이터 객체를 spread 연산자를 통해 iterable array로 바꿀 수 있음

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

let generator = generatorFunc();
for (let value of generator) {
  console.log(value);
}
// 1, 2

let sequence = [...generatorFunc()];
console.log(sequence); // [1, 2]

이터러블 대신 제너레이터 사용하기

  • iterator 간단하게 알아보기

    let obj = {
      one: 1,
      two: 2,
    };
    
    console.log(...obj);
    // TypeError: Found non-callable @@iterator

    객체는 iterable하지 않다. spread 연산자는 사용할 수 있다.

    key: value 로 이루어진 쌍이므로, 출력은 불가능하다.

    단, iterator 객체를 사용할 경우 next() 메서드를 사용해 간단한 범위 반복자를 형성할 수 있다.

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

  [Symbol.iterator]() {
    return {
      current: this.from,
      last: this.to,
      next() {
        if (this.current <= this.last) {
          return { done: false, value: this.current++ };
        } else {
          return { done: true };
        }
      },
    };
  },
};

console.log(...range); // 1, 2, 3, 4, 5

// generator
range = {
  from: 1,
  to: 5,

  *[Symbol.iterator]() {
    // [Symbol.iterator]: function*()를 짧게 줄임
    for (let value = this.from; value <= this.to; value++) {
      yield value;
    }
  },
};

제너레이터 컴포지션

제너레이터 안에 제너레이터를 임베딩할 수 있는 기능

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);

  // for (let i = 48; i <= 57; i++) yield i;

  // for (let i = 65; i <= 90; i++) yield i;

  // for (let i = 97; i <= 122; i++) yield i;
}

let str = "";

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

console.log(str); // 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz

'yield'를 사용해 제너레이터 안.밖으로 정보 교환하기

generator.next(arg) : 값을 안, 밖으로 전달할 수 있다. arg는 yield의 결과

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

  console.log(ask1); // 4

  let ask2 = yield "3 * 3 = ?";

  console.log(ask2); // 9
}

let generator = gen();

console.log(generator.next().value); // **"2 + 2 = ?"** 

console.log(generator.next(4).value); // "3 * 3 = ?"

console.log(generator.next(9).done); // true
  1. 첫 generator.next() 메서드는 인자의 유무에 상관없이 첫 yield의 값을 받는다.
    "2 + 2 = ?"
  2. 두번째 generator.next(4) 의 argument인 4는 바로 전 yield가 들어간 ask1의 위치에 들어간다. 그리고 두번째 yield의 값을 받은 객체를 받는다.
    ask1 = 4
    { value:"3 * 3 = ?", done: false }
  3. 세번째 generator.next(9)의 argument인 9 는 바로 전 yield가 들어간 ask2의 위치에 들어간다. 그리고 다음 yield가 없기 때문에 { value: undefined, done: true }를 받는다.
  • 제너레이터와 프로미스를 이용한 비동기 처리!

generator.throw

외부코드가 에러를 만들거나 던질 수 있다.

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

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

let generator = gen();

let question = generator.next().value;
console.log(question); // 2 + 2 = ?

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

(2) 에서 제너레이터로 던진 에러는 yield와 함께 (1)로 던져진다.

던져진 에러는 catch로 전달된다.

제너레이터 바깥에서 error를 잡을 수도 있다.

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); // 에러 출력
profile
공동의 성장을 추구하는 개발자

0개의 댓글