본문 바로가기
Learn to Code

[JS] 제너레이터란(Generator)?!

by CEOSEO 2021. 10. 2.
728x90
반응형

 

최근 한 인터뷰에서 자바스크립트의 yield를 알고 있는지 물어보셨다. 그런데 너무 당황스럽게도 6개월이 넘는 자바스크립트 공부 기간 동안 yield라는 키워드는 단 한번도 들어본적이 없었다!!!! (왜냐!! 왜 때문이냐!!?? 나란 개발자는나쁜 개발자더냐??) 🥲 그래서 인터뷰가 끝나자마자 yield를 검색해보았더니...

 

출처: MDN

 

yield 키워드는 제너레이터 함수를 멈추거나 다시 시작하게 하는데 사용되는 키워드라고 한다. 이 한 줄 설명을 보고 내가 yield 키워드를 못들어봤던 이유를 클리어하게 알게 되었다. 왜냐면 난 제너레이터 함수가 뭔지도 모르기 때문이다 🥲. (멈추거나 다시 시작하게 하는 대상이 무엇인지도 모르는데 그 키워드를 어찌 알았겠어...) 그래서 제너레이터 함수가 무엇인지부터 이해하고 그 이후 yield가 어떤 키워드인지를 알아보기로 했다.

 

 

 

 

제너레이터(Generator)란?

제너레이터는 함수이다. 그런데 그냥 일반 함수가 아니라 좀 특수한 함수이다. 제너레이터(Generator)중간에 원하는 부분에서 멈추었다가, 그 부분부터 다시 실행할 수 있는 능력을 가진 함수이다. 이 정의만 읽어서는 그렇게 와닿지 않는데, 우리가 아는 일반 함수와 비교하면서 보니 이해가 더 쉬웠다. 원래 새로운 지식은 기존 지식과 비교하며 배울 때 더 습득이 잘 되는 거니까!

 

 

 

제너레이터 함수 사용하기

제너레이터 함수는 function* 키워드를 사용하며, 아래와 같이 작성하면 된다:

 

// 제너레이터

function* generatorFunc1() {...}

const generatorFunc2 = function* () {...}

 

제너레이터 함수는 화살표 함수를 사용할 수 없다 (MDN: 화살표 함수).

 

별표 * (asterisk)의 위치는 function 키워드와 함수 이름 사이면 아무데나 붙여도 되지만, 일관성 유지 목적으로 function 키워드 바로 뒤에 붙이는 것이 권장된다고 한다 (출처).

 

 

 

제너레이터 함수 vs 일반 함수 

1. 제너레이터 함수는 함수 호출자에게 함수 실행의 제어권을 양도(yield)할 수 있다.

일반 함수 같은 경우엔, 함수가 어딘가에서 호출되면 그 함수에 대한 제어권은 호출된 함수 본인에게 넘어간다. 예를 들어, 남자 사람 A의 친구 B가 A에게 소개팅 주선을 해주었다고 생각해보자. 소개팅 주선자 B는 함수 호출자(function caller)이며, 소개팅에 참여하는 A는 일반 함수이다. 소개팅 주선은 함수 호출자 B가 해주었지만 소개팅에 어떻게 임할지는 전적으로 일반 함수 A에게 달렸다.

 

하지만 우리의 제너레이터 함수는 일반 함수와는 격이 다르다. 제너레이터 함수는 함수 실행의 제어권을 함수 호출자에게 양도할 수 있다. 즉, 함수 호출자는 함수 실행을 일시 중지시키거나 다시 시작하게 하도록 할 수 있다. 소개팅 예시에 빗대자면 이는 마치 소개팅 참가자(=제너레이터 함수)가 참여하는 소개팅은 일종의 아바타 소개팅이 되는 것과 같다.

 

 

 

 

2. 제너레이터 함수는 함수 호출자와 함수의 상태를 주고받을 수 있다.

일반 함수는 호출되는 순간에 매개변수(parameter)를 통해 함수 외부에서부터 값을 전달 받고 실행된다. 이는 즉 함수가 실행되고 있는 동안에는 함수 외부에서 함수 내부로 값을 전달하여 함수의 상태를 변경할 수 없다는 것을 뜻한다. 이미 소개팅 장소에 당사자들이 모인 이후라면, 남자 사람 A는 예쁘게 보이고 싶어 목에 걸고 나온 사슬 금목걸이를 벗을 기회를 영영 잃게 된다는 것이다. 약속 장소에서 서로를 인지하게 된 이상 여자는 이미 그 목걸이를 발견했고 한 번의 동공 지진을 벌써 겪었을 것이기 때문이다.

 

반면 제너레이터 함수는 다르다. 제너레이터 함수는 함수 호출자와 양방향으로 함수의 상태를 주고받을 수 있다. 즉, 제너레이터 함수는 함수 호출자에게 자신의 상태를 전달할 수 있고, 함수 호출자로부터 추가적으로 상태를 전달받을 수도 있다.

 

 

함수호출자: "금을 녹일 수 있는 물질이다. 받자마자 사용해라"

 

 

3. 제너레이터 함수는 호출 시 제너레이터 객체를 생성해 반환한다.

일반 함수는 호출이 되면 함수의 코드 블록을 실행시킨다. 반면, 제너레이터 함수는 코드 블록을 실행시키는 것이 아니라 제너레이터 객체를 생성해서 반환한다.

 

제너레이터 객체(Generator object)란?

제너레이터 함수를 호출했을 때 리턴되는 객체이다. 제너레이터 객체는 이터러블(iterable)이면서 동시에 이터레이터(iterator)이다.

 

1) 이터러블(iterable)

이터러블은 이터러블 프로토콜을 준수한 객체를 뜻한다. 여기서 '이터러블 프로토콜을 준수했다'는 것은 Symbol.iterator를 프로퍼티로 사용한 메소드를 직접 구현하거나, 프로토타입 체인을 통해 상속받은 객체를 말한다. 배열, 문자열, Map, 그리고 Set도 모두 이터러블이다.

 

제너레이터 객체는 이터러블이기 때문에 for... of 문을 사용할 수 있으며, 스프레드 문법과 배열 디스트럭처링도 사용할 수 있다.

 

2) 이터레이터(iterator)

이터레이터는 이터러블에 Symbol.iterator 메소드를 호출했을 때 반환되는 값이다:

 

const maArr = [1, 2, 3]
const iterator = maArr[Symbol.iterator]()

 

이터레이터는 next라는 메소드를 가지고 있는데, 이걸 이용해서 이터러블을 각 요소를 순회할 수 있다. next 메소드를 호출하면, 이터레이터 리절트 객체 (Iterator result object)가 반환된다. 이터레이터 리절트 객체는 value와 done이라는 프로퍼티를 갖고 있다. 

 

const myArr = [1, 2, 3]
const iterator = myArr[Symbol.iterator]()
const iteratorResultObject = iterator.next()
console.log(iteratorResultObject) // {value: 1, done: false}

 

제너레이터 객체도 이터레이터이기 때문에, next 메소드를 사용할 수 있다.

 

 

제너레이터 객체의 next, return, 그리고 throw 메소드

제너레이터 객체는 이터레이터이기 때문에 next 메소드를 사용할 수 있다. 하지만 이터레이터에는 없는 두 가지 메소드를 추가로 더 사용할 수 있는데, 그것이 바로 return과 throw이다.

 

// 썸남과의 부적절한 밀당 사례를 제너레이터 함수와 next로 표현했다
function* inappropriateMildang () {
  try {
    yield "씹는다"
    yield "못본척한다"
    yield "바쁜척한다"
    yield "괜히 친구랑 통화를 더 길게 한다"
    yield "카톡의 1이 사라지지 않게 한다"
    yield "답장을 보낸다"
  } catch(e) {
    console.log(e)
  }
}

const generator = inappropriateMildang()

const first = generator.next() // {value: "씹는다", done: false}
const second = generator.next() // {value: "못본척한다", done: false}

...

const sixth = generator.next() // {value: "답장을 보낸다", done: false}
const seventh = generator.next() // {value: undefined, done: true}

// 연애는 undefined이면서 done된 걸로...

 

generator.return(parameter)은 인자로 전달받은 parameter를 value의 값으로, true를 done으로 하는 이터레이터 리절트 객체를 리턴한다.

 

generator.throw(parameter)는 인자로 전달받은 에러를 발생시키면서, value 값으론 undefined를, done 값으론 true를 갖는 이터레이터 리절트 객체를 리턴한다.

 

 

yield와 next

yield는 제너레이터 함수를 멈추거나 다시 시작하는데 사용하는 키워드이다. 위 코드 예제에서 볼 수 있는 것과 같이, next 메소드를 사용하면 yield 키워드가 사용된 표현식까지 실행되고 함수가 일시 중지된다. 바로 이때 함수의 제어권이 함수 호출자(function caller)게 양도된다.  즉, next 메소드가 반복 호출될 때마다 다음 yield 표현식까지 실행과 중지가 반복되는 것이다. 

 

위에 설명한 제너레이터 함수의 특징 중엔 함수 호출자와 제너레이터 함수가 상태를 보고 새로 전달할수도 있다고 했다. 그건 어떻게 하는 것일까? 바로 next 메소드를 사용하는 것이다. next 메소드는 제너레이터가 아닌 일반 이터레이터에서 호출될 땐 parameter가 필요 없지만, 제너레이터에선 인자를 전달할 수 있다.

 

function* possibilityOfHavingTheThirdDate () {
  const firstImpression = yield -1000
  const secondDate = yield (firstImpression + 100)
  return firstImpression + secondDate
}

const generator = possibilityOfHavingTheThirdDate ()

// 3번의 next 호출
let first = generator.next() // {value: -1000, done: false}
let second = generator.next(-50) // {value: 50, done: false}
let third = generator.next(200) //{value: 150, done: true}

 

 

 

 

참고

모던 자바스크립트 Deep Dive

MDN Generator

 

 

 

 

728x90
반응형

댓글