제너레이터

se-een·2023년 3월 27일
0
post-thumbnail

이 글은 '이웅모'님의 '모던 자바스크립트 Deep Dive' 책을 통해 공부한 내용을 정리한 글입니다. 저작권 보호를 위해 책의 내용은 요약되었습니다.

제너레이터

ES6에서 도입되었으며, 코드 블록을 일시 중지했다가 필요한 시점에서 재개할 수 있는 특수한 함수이다.

제너레이터 특징

제너레이터는 다음과 같은 특징이 있다.

  • 함수 호출자에게 함수 실행 제어권을 양도할 수 있다.
  • 함수 호출자와 함수 상태를 주고받을 수 있다.
  • 제너레이터 함수를 호출하면 제너레이터 객체를 반환한다.

제너레이터 함수의 정의

제너레이터 함수는 function* 키워드로 선언한다. 그리고 하나 이상의 yield 표현식을 포함한다.

function* genFunc() {
  yield 1;
}

function 키워드 바로 뒤에 *를 붙이는 것을 권장하며, 화살표 함수와 생성자 함수 이외에 모든 형태로 선언할 수 있다.

const genFunc = * () => {} // SyntaxError

function* GenFunc() {}
new GenFunc(); // TypeError

제너레이터 객체

제너레이터 함수를 호출하면 일반 함수처럼 함수 코드 블록을 실행하는 것이 아니라, 제너레이터 객체를 반환한다. 이 객체는 이터러블 하면서 동시에 이터레이터이다.

즉, 제너레이터 객체는 Symbol.iterator 메서드를 상속받는 이터러블이면서 value, done 프로퍼티를 갖는 이터레이터 리절트 객체를 반환하는 next 메서드를 소유하는 이터레이터이다.

게다가 이터레이터에 없는 return, throw 메서드도 갖는다.

function* genFunc() {
  try {
    yield 1;
    yield 2;
  } catch(e) {
    console.error(e);
  }
}

const generator = genFunc()

console.log(generator.next()); // {value: 1, done: false}
console.log(generator.return('return this')); // {value: 'return this', done: true}
console.log(generator.throw('error!')); // {value: undefined, done: true}
  • next 메서드 호출 : yield 표현식까지 차례로 코드 블록 실행 / yield 된 값을 value 프로퍼티 값으로, false를 갖는 done 프로퍼티로 리절트 객체를 반환
  • return 메서드 호출 : 인수로 전달받은 값을 value 프로퍼티 값으로, true를 갖는 done 프로퍼티로 리절트 객체를 반환
  • throw 메서드 호출 : 인수로 전달받은 값으로 에러를, undefinedvalue 프로퍼티 값으로, true를 갖는 done 프로퍼티로 리절트 객체를 반환

제너레이터 일시 중지와 재개

yield 키워드와 next 메서드를 통해 실행을 일시 중지했다가 필요한 시점에 다시 재개할 수 있다. 즉, 제너레이터 함수는 일반 함수와 다르게 함수 호출자에게 제어권을 양도하여 필요한 시점에서 함수 실행을 재개할 수 있다.

function* genFunc() {
  console.log(1);
  yield 1;
  
  console.log(2);
  yield 2;
  
  console.log(3);
  yield 3;
}

const generator = genFunc();

console.log(generator.next());
// 1
// {value: 1, done: false}

console.log(generator.next());
// 2
// {value: 2, done: false}

console.log(generator.next());
// 3
// {value: 3, done: false}

console.log(generator.next());
// {value: undefined, done: true}

이터레이터의 next 메서드와 달리 제너레이터 객체의 next 메서드는 인수를 전달할 수 있다. 바로 이 부분이 제너레이터의 실행 흐름을 헷갈리게 하는 요인이라고 생각하는데 실행 흐름은 다음과 같다.

제너레이터 객체의 next 메서드에 전달한 인수는 제너레이터 함수의 yield 표현식을 할당받는 변수에 할당된다. 단, yield 표현식을 할당 받는 변수에 yield 표현식의 평가 결과가 할당되는 것은 아니다.

function* genFunc() {
  const a = yield 1;
  
  const b = yield (a + 2);
  
  // 제너레이터에서 return은 반환의 의미가 아닌 종료의 의미로 사용한다.
  return a + b;
}

const generator = genFunc();

console.log(generator.next()); // {value: 1, done: false}
console.log(generator.next(10)); // {value: 12, done: false}
console.log(generator.next(30)); // {value: 40, done: true}

실행 흐름을 보자. 첫 next()에는 인자 값을 줄 수 없다. 주더라도 무시된다. next 메서드를 호출하면 첫 yield 표현식까지 실행하고 그 결과값을 value 프로퍼티에 담아서 준다.

두 번째 next 메서드를 실행할 때 인자로 10을 담아서 넘겨주고 있다. 이 10이 b에 할당될 것 같지만, a에 할당된다.

그리고 value 프로퍼티에 a+210+2가 되므로 12가 할당된다. b에 12가 할당된다고 생각하면 안 된다.

마지막 next 메서드를 실행할 때 인자로 30을 담아서 넘겨준다. 이 30은 b에 할당된다. 더 이상의 yield문이 없으므로 함수 끝까지 실행되며 이 때의 return 값이 value 프로퍼티의 값이 된다. 즉 value 프로퍼티에 10+30, 40이 할당된다.

이처럼 제너레이터는 가독성이 좋지 않다. 그래서 ES8부터는 async / await를 도입하여 제너레이터를 대신하여 비동기를 동기처럼 작동하도록 구현했다.

profile
woowacourse 5th FE

0개의 댓글