본문 바로가기

제너레이터 함수

function*와 yield로 실행을 멈추었다가 이어 가는 제너레이터의 동작을 next 호출과 출력으로 추적하고, Generator의 세 타입 매개변수, yield*의 위임, 무한 시퀀스와 이터러블 구현, return과 throw로 바깥에서 흐름을 바꾸는 방법, 비동기 제너레이터와 for await를 다룹니다.

일반 함수는 한 번 부르면 끝까지 실행된 뒤 값을 하나 돌려줍니다. 제너레이터 함수(generator function)는 실행하다가 yield를 만나면 값을 하나 내보내고 그 자리에서 멈추었다가, 바깥에서 다음 값을 요청하면 멈춘 곳부터 이어서 실행합니다. 그래서 값을 한꺼번에 만들어 두지 않고 필요할 때마다 하나씩 만들어 낼 수 있습니다.

이 글은 제너레이터가 멈추고 이어지는 순서를 next 호출과 출력으로 추적하고, 그 타입을 적는 방법을 봅니다. 이어서 다른 반복 가능한 값에 일을 맡기는 yield*, 끝없는 시퀀스와 직접 만든 이터러블, 바깥에서 흐름을 끝내거나 오류를 넣는 방법을 보고, 마지막으로 비동기 제너레이터를 다룹니다.

멈추고 이어지는 함수

제너레이터 함수는 function*으로 선언합니다. 이 함수를 부르면 본문이 바로 실행되지 않고, 실행을 조종할 수 있는 제너레이터 객체가 돌아옵니다. 그 객체의 next()를 부를 때마다 본문이 다음 yield까지 실행되고, 내보낸 값을 { value, done } 모양의 결과로 돌려줍니다.

function* simpleGenerator(): Generator<number, string, boolean> {
  console.log("본문 시작");
  let input = yield 1;
  console.log("첫 yield 뒤 재개, 입력값:", input);
  input = yield 2;
  console.log("둘째 yield 뒤 재개, 입력값:", input);
  return "모든 작업 완료!";
}

const generator = simpleGenerator();
console.log("객체 생성 완료");
console.log(generator.next());
console.log(generator.next(true));
console.log(generator.next(false));
console.log(generator.next());
객체 생성 완료
본문 시작
{ value: 1, done: false }
첫 yield 뒤 재개, 입력값: true
{ value: 2, done: false }
둘째 yield 뒤 재개, 입력값: false
{ value: '모든 작업 완료!', done: true }
{ value: undefined, done: true }

simpleGenerator()를 불렀을 때는 아무것도 출력되지 않았고, 첫 next()에서야 본문이 시작되어 yield 1에서 멈춥니다. 두 번째 호출의 next(true)에서 true는 멈추어 있던 yield 1이라는 식의 값이 되어 input에 들어가고, 본문은 다음 yield 2까지 실행됩니다. 마지막 return의 값은 done: true인 결과에 담기며, 끝난 뒤의 next()는 언제나 { value: undefined, done: true }를 돌려줍니다.

Generator는 caller와 frame 사이에서 제어와 값을 교환한다 — 상태 추적

next(), next(true), next(false)에 따라 실행 지점과 입력/출력/done이 바뀐다. 첫 next 인자와 시작 전 return/throw 경계를 구분한다.

교재 simpleGenerator의 next(), next(true), next(false) 요청과 1, 2, 완료 결과를 caller와 generator 두 lifeline 사이에서 교환한다.

GENERATOR / CONTROL AND VALUE

next 입력과 yield·return 결과를 실제 호출 순서로 읽는다

simpleGenerator() 호출은 iterator를 만들고 본문은 첫 next에서 시작합니다. 이후 next 인자가 직전 yield 표현식의 값이 됩니다. 최종 return 값은 done: true 결과에 담깁니다.

next 입력과 yield·return 결과를 실제 호출 순서로 읽는다교재 simpleGenerator의 next(), next(true), next(false) 요청과 1, 2, 완료 결과를 caller와 generator 두 lifeline 사이에서 교환한다.Callernext 입력Generator중단·재개next() → 본문 시작{ value: 1, done: false }next(true) → 첫 yield에 true{ value: 2, done: false }next(false) → 둘째 yield에 false{ value: 완료 문자열, done: true }
next 입력과 yield 결과를 시간 순서로 교환한다호출자와 제너레이터의 두 lifeline에서 next(), 값1·done false, next(true), 값2·done false, next(false), 최종 문자열 모든 작업 완료!·done true를 교환한다. 첫 yield 입력은 true, 둘째 입력은 false다. 반환 메시지는 점선이며 시간은 위에서 아래로 흐른다.호출자Caller제너레이터Generatornext()본문 시작value: 1done: falsenext(true)첫 yield에true 입력value: 2done: falsenext(false)둘째 yield에false 입력모든 작업완료!done: true

START

첫 next 입력은 사용하지 않는다

첫 요청 앞에는 값을 받을 yield가 없습니다. 교재의 첫 next()는 실행을 시작해 1을 반환합니다.

TYPE CHANNELS

입력·중간 값·최종 값은 별개

Generator<number, string, boolean>은 yield 숫자, return 문자열, next 입력 boolean을 구분합니다. 이후 완료된 next는 undefined와 done: true를 돌려줍니다.

THROW / RETURN

중단 상태에 따라 종료가 다르다

진입한 try/finally는 throw·return 처리 중 실행할 수 있습니다. 시작 전 return/throw는 본문에 진입하지 않습니다. finally가 yield하면 return도 즉시 done: true가 아닐 수 있습니다.

YIELD STAR

위임은 별도의 반복자와 교환

yield*는 다른 iterable의 값을 전달합니다. 교재의 배열·anotherGenerator 위임은 simpleGenerator의 위 시퀀스와 별도 예제입니다.

LESSON LIMIT

간단한 비동기 runner의 한계

원문의 Promise 실패 콜백은 generator.throw의 결과·예외를 외부 Promise에 연결하지 않습니다. 이 그림은 그 runner가 실패·정리를 완성했다고 보장하지 않습니다.

next·throw·return은 호출자의 제어 요청입니다. 지연 실행만으로 외부 자원 정리가 구현되지는 않습니다.

이 예의 조건

  • 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
  • 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
관계도의 보충 설명
원도표의 전체 관계·조건·설명
구분내용
전체 관계·조건 설명호출자와 제너레이터의 두 lifeline에서 next(), 값 1·done false, next(true), 값 2·done false, next(false), 최종 문자열 모든 작업 완료!·done true를 교환한다. 첫 yield 입력은 true, 둘째 입력은 false다. 반환 메시지는 점선이며 시간은 위에서 아래로 흐른다.
원도표 설명Generator<number, string, boolean>은 yield 숫자, return 문자열, next 입력 boolean을 구분합니다. 이후 완료된 next는 undefined와 done: true를 돌려줍니다.
전체 상태와 변화 근거
장면현재 관찰값설명변화 이유
첫 next()value: 1 / done: falsenext()가 yield 1까지 실행된다첫 next의 인자는 첫 yield의 입력으로 쓰이지 않는다
next(true)입력: true / 결과: value:2, done:falsetrue를 이전 yield 표현식에 넣고 yield 2까지 재개한다next의 입력과 yield의 출력은 방향이 다르다
next(false)value: 모든 작업 완료! / done: truefalse로 재개한 뒤 모든 작업 완료!를 반환한다return 결과는 일반 yield 항목으로 소비되지 않는다
완료 뒤 next()value: undefined / done: true끝난 iterator는 value undefined와 done:true를 반환한다이미 완료된 본문은 다시 실행되지 않는다

Generator는 caller와 frame 사이에서 제어와 값을 교환한다 — 상태 추적

next(), next(true), next(false)에 따라 실행 지점과 입력/출력/done이 바뀐다. 첫 next 인자와 시작 전 return/throw 경계를 구분한다.

교재 simpleGenerator의 next(), next(true), next(false) 요청과 1, 2, 완료 결과를 caller와 generator 두 lifeline 사이에서 교환한다.

GENERATOR / CONTROL AND VALUE

next 입력과 yield·return 결과를 실제 호출 순서로 읽는다

simpleGenerator() 호출은 iterator를 만들고 본문은 첫 next에서 시작합니다. 이후 next 인자가 직전 yield 표현식의 값이 됩니다. 최종 return 값은 done: true 결과에 담깁니다.

next 입력과 yield·return 결과를 실제 호출 순서로 읽는다교재 simpleGenerator의 next(), next(true), next(false) 요청과 1, 2, 완료 결과를 caller와 generator 두 lifeline 사이에서 교환한다.Callernext 입력Generator중단·재개next() → 본문 시작{ value: 1, done: false }next(true) → 첫 yield에 true{ value: 2, done: false }next(false) → 둘째 yield에 false{ value: 완료 문자열, done: true }
next 입력과 yield 결과를 시간 순서로 교환한다호출자와 제너레이터의 두 lifeline에서 next(), 값1·done false, next(true), 값2·done false, next(false), 최종 문자열 모든 작업 완료!·done true를 교환한다. 첫 yield 입력은 true, 둘째 입력은 false다. 반환 메시지는 점선이며 시간은 위에서 아래로 흐른다.호출자Caller제너레이터Generatornext()본문 시작value: 1done: falsenext(true)첫 yield에true 입력value: 2done: falsenext(false)둘째 yield에false 입력모든 작업완료!done: true

START

첫 next 입력은 사용하지 않는다

첫 요청 앞에는 값을 받을 yield가 없습니다. 교재의 첫 next()는 실행을 시작해 1을 반환합니다.

TYPE CHANNELS

입력·중간 값·최종 값은 별개

Generator<number, string, boolean>은 yield 숫자, return 문자열, next 입력 boolean을 구분합니다. 이후 완료된 next는 undefined와 done: true를 돌려줍니다.

THROW / RETURN

중단 상태에 따라 종료가 다르다

진입한 try/finally는 throw·return 처리 중 실행할 수 있습니다. 시작 전 return/throw는 본문에 진입하지 않습니다. finally가 yield하면 return도 즉시 done: true가 아닐 수 있습니다.

YIELD STAR

위임은 별도의 반복자와 교환

yield*는 다른 iterable의 값을 전달합니다. 교재의 배열·anotherGenerator 위임은 simpleGenerator의 위 시퀀스와 별도 예제입니다.

LESSON LIMIT

간단한 비동기 runner의 한계

원문의 Promise 실패 콜백은 generator.throw의 결과·예외를 외부 Promise에 연결하지 않습니다. 이 그림은 그 runner가 실패·정리를 완성했다고 보장하지 않습니다.

next·throw·return은 호출자의 제어 요청입니다. 지연 실행만으로 외부 자원 정리가 구현되지는 않습니다.

이 예의 조건

  • 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
  • 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.

관계도의 보충 설명

원도표의 전체 관계·조건·설명
구분내용
전체 관계·조건 설명호출자와 제너레이터의 두 lifeline에서 next(), 값 1·done false, next(true), 값 2·done false, next(false), 최종 문자열 모든 작업 완료!·done true를 교환한다. 첫 yield 입력은 true, 둘째 입력은 false다. 반환 메시지는 점선이며 시간은 위에서 아래로 흐른다.
원도표 설명Generator<number, string, boolean>은 yield 숫자, return 문자열, next 입력 boolean을 구분합니다. 이후 완료된 next는 undefined와 done: true를 돌려줍니다.
전체 상태와 변화 근거
장면현재 관찰값설명변화 이유
첫 next()value: 1 / done: falsenext()가 yield 1까지 실행된다첫 next의 인자는 첫 yield의 입력으로 쓰이지 않는다
next(true)입력: true / 결과: value:2, done:falsetrue를 이전 yield 표현식에 넣고 yield 2까지 재개한다next의 입력과 yield의 출력은 방향이 다르다
next(false)value: 모든 작업 완료! / done: truefalse로 재개한 뒤 모든 작업 완료!를 반환한다return 결과는 일반 yield 항목으로 소비되지 않는다
완료 뒤 next()value: undefined / done: true끝난 iterator는 value undefined와 done:true를 반환한다이미 완료된 본문은 다시 실행되지 않는다

위 그림은 이 교환을 호출하는 쪽과 제너레이터 사이의 순서로 보여 줍니다. 값은 yield를 통해 바깥으로 나가고, next의 인수를 통해 안으로 들어옵니다. 첫 next에 넘긴 인수는 받을 yield가 아직 없으므로 쓰이지 않습니다.

Generator<number, string, boolean>의 세 타입 매개변수는 차례로 yield로 내보내는 값, return으로 돌려주는 값, next로 받아들이는 값의 타입입니다. 그래서 generator.next("문자열")처럼 넘기면 boolean이 아니어서 진단되고, yield 1이라는 식의 타입은 받아들이는 값인 boolean입니다. 나가는 값 1과 들어오는 값 true는 서로 다른 값이라는 점을 기억해야 합니다.

yield*로 맡기기

yield* 뒤에 다른 이터러블을 두면, 그 이터러블이 끝날 때까지 그 값들을 대신 내보냅니다. 배열이나 문자열, 다른 제너레이터에 일을 맡겨 여러 시퀀스를 하나로 이을 수 있습니다.

function* anotherGenerator(): Generator<number> {
  yield 5;
  yield 6;
}

function* combined(): Generator<number> {
  yield 1;
  yield* [2, 3, 4];
  yield* anotherGenerator();
  yield 7;
}

console.log([...combined()].join(" "));
1 2 3 4 5 6 7

전개 구문 [...combined()]은 제너레이터가 끝날 때까지 next를 불러 모든 값을 배열로 모으므로, 일곱 개의 숫자가 차례로 나옵니다. for...of도 같은 방식으로 값을 꺼내며, 둘 다 done: true인 결과의 value, 곧 return의 값은 항목으로 세지 않습니다.

필요할 때 만드는 시퀀스

제너레이터는 다음 값이 요청될 때만 실행되므로, 끝이 없는 시퀀스도 만들 수 있습니다. 전체를 미리 만들어 두는 배열과 달리, 요청한 만큼만 계산합니다.

function* idGenerator(): Generator<number, never, void> {
  let id = 0;
  while (true) {
    yield id++;
  }
}

const ids = idGenerator();
console.log(ids.next().value, ids.next().value, ids.next().value);
0 1 2

while (true)는 끝나지 않지만, yield에서 멈추므로 프로그램이 멈추지는 않습니다. 이 제너레이터는 절대 return하지 않으므로 반환 타입을 never로 적었습니다. 다만 이런 제너레이터를 전개 구문이나 끝을 정하지 않은 for...of로 모두 꺼내려 하면 끝나지 않으므로, 필요한 개수만큼만 요청해야 합니다.

제너레이터 객체는 그 자체로 이터러블이므로, 클래스의 [Symbol.iterator] 메서드를 제너레이터로 만들면 그 클래스의 객체를 for...of로 돌 수 있습니다.

class MyCollection<T> {
  constructor(private items: T[]) {}

  *[Symbol.iterator](): Generator<T> {
    for (const item of this.items) {
      yield item;
    }
  }
}

for (const num of new MyCollection([10, 20, 30])) {
  console.log(num);
}
10
20
30

for...of는 시작할 때 [Symbol.iterator]()를 불러 새 제너레이터를 얻으므로, 같은 컬렉션을 여러 번 돌아도 매번 처음부터 시작합니다. 반면 이미 만든 제너레이터 객체 하나를 두 번 돌면, 두 번째에는 첫 번째에서 멈춘 상태를 이어서 쓰므로 남은 값이 없을 수 있습니다.

더 깊이: 이터레이터에 직접 붙은 map과 take

끝없는 제너레이터에서 필요한 개수만큼만 꺼내는 일을 이제는 표준 메서드로 할 수 있습니다. ECMAScript의 이터레이터 도우미(iterator helpers)가 들어오면서, 제너레이터가 만든 객체에도 map, filter, take 같은 메서드가 생겼습니다. TypeScript 5.6 릴리스 노트의 예처럼 끝없는 양의 정수 제너레이터에 map(x => x * 2)를 붙이고 take(5)로 다섯 개만 돌면 2, 4, 6, 8, 10이 나옵니다. 이 메서드들은 배열의 같은 이름의 메서드와 달리 결과를 미리 모두 만들지 않고, 값을 요청받을 때마다 원래 이터레이터에서 하나씩 꺼내 처리합니다. Map과 Set의 keys(), values(), entries()도 같은 메서드를 가지며, 다른 이터러블은 Iterator.from으로 감싸 쓸 수 있습니다.

이 메서드의 타입은 표준 라이브러리 설정에 따라 보입니다. TypeScript 6.0은 이 Iterator 메서드의 선언을 esnext에서 es2025로 옮겼으므로, target이나 lib가 ES2025 이상이어야 쓸 수 있습니다. 이 교재의 ts-first처럼 target을 es2022로 둔 프로젝트라면 lib에 es2025를 더해야 하고, 실행 환경도 이 메서드를 지원해야 합니다. 5.6은 이런 내장 이터레이터를 나타내는 IteratorObject 타입도 들였으며, 9장의 strictBuiltinIteratorReturn이 켜져 있으면 내장 이터레이터의 반환 타입은 any가 아니라 undefined로 다루어집니다.

return과 throw

제너레이터 객체에는 next 말고도 바깥에서 흐름을 바꾸는 두 메서드가 있습니다. throw(error)는 멈추어 있는 yield의 자리에서 그 오류를 던진 것처럼 만들고, return(value)는 그 자리에서 return한 것처럼 만들어 제너레이터를 끝냅니다.

function* errorHandlingGenerator(): Generator<number, number> {
  try {
    yield 1;
    yield 2;
  } catch (error) {
    console.log("내부에서 포착:", error instanceof Error ? error.message : error);
  } finally {
    console.log("정리 작업");
  }
  return 4;
}

const errGen = errorHandlingGenerator();
console.log(errGen.next());
console.log(errGen.throw(new Error("외부에서 주입한 오류")));
console.log(errGen.next());
{ value: 1, done: false }
내부에서 포착: 외부에서 주입한 오류
정리 작업
{ value: 4, done: true }
{ value: undefined, done: true }

yield 1에서 멈추어 있을 때 throw를 부르면 그 자리에서 오류가 생기므로, 본문의 catch가 받아 메시지를 출력하고 finally를 지나 return 4로 끝납니다. 그래서 throw 호출의 결과가 { value: 4, done: true }이고, yield 2는 실행되지 않습니다. 본문에 catch가 없었다면 그 오류는 throw를 부른 쪽으로 다시 던져집니다.

비동기 제너레이터

async function*으로 선언한 비동기 제너레이터는 본문에서 await를 쓸 수 있고, next()가 결과를 담은 Promise를 돌려줍니다. 이런 값은 for await...of로 하나씩 기다리며 꺼냅니다.

async function* userStream(): AsyncGenerator<string, void, void> {
  for (const [index, user] of ["Alice", "Bob", "Charlie"].entries()) {
    console.log(`[Gen] 사용자 ${index + 1} 요청`);
    await new Promise((resolve) => setTimeout(resolve, 50));
    yield user;
  }
  console.log("[Gen] 스트림 종료");
}

async function processUserStream(): Promise<void> {
  for await (const user of userStream()) {
    console.log(`처리: ${user}`);
  }
  console.log("모든 사용자 처리 완료");
}

processUserStream();
[Gen] 사용자 1 요청
처리: Alice
[Gen] 사용자 2 요청
처리: Bob
[Gen] 사용자 3 요청
처리: Charlie
[Gen] 스트림 종료
모든 사용자 처리 완료

for await가 다음 값을 요청할 때마다 제너레이터는 한 사용자를 기다렸다가 내보내고, 반복문의 본문이 그 값을 처리한 뒤에야 다음 값을 요청합니다. 그래서 요청과 처리가 한 사용자씩 번갈아 출력되며, 소비하는 쪽이 값을 당겨 가는 속도에 맞추어 만들어집니다. 마지막 값을 처리한 뒤의 요청에서 제너레이터의 반복문이 끝나 스트림 종료가 출력되고, 그다음에 바깥 반복문이 끝납니다.

일반 제너레이터의 next는 다음 yield까지 동기적으로 실행된다는 점도 구분해야 합니다. yield 앞에서 오래 계산하면 그 next 호출도 그만큼 오래 걸리며, 제너레이터라는 이유만으로 무거운 계산이 비동기가 되지는 않습니다. async와 await가 생기기 전에는 일반 제너레이터가 Promise를 yield하고 실행기가 그것을 기다려 주는 방식으로 비동기 흐름을 쓰기도 했는데, 지금은 대부분 async 함수가 그 역할을 맡습니다.

즉시 값, Promise, 배열, generator, async generator와 정리 책임을 같은 6카드로 비교한다.

LAZY SEQUENCE / CHOICE

값의 개수·생산 시점·요청 방식을 비교한다

단일 결과와 여러 값, 이미 계산된 값과 요청할 때 생산하는 값을 구분합니다. generator와 async generator는 동기·비동기 pull 반복을 간단히 구현하는 도구입니다.

SINGLE SYNC

즉시 값

단일 결과가 이미 있거나 동기 계산이면 값을 반환합니다. 반복 프로토콜은 필요하지 않을 수 있습니다.

SINGLE ASYNC

Promise

한 번 정착하는 비동기 결과를 표현합니다. 여러 항목을 차례로 요청하는 프로토콜과는 다릅니다.

READY SEQUENCE

배열·기존 iterable

이미 모인 값을 순회할 때 배열을 사용할 수 있습니다. 모든 iterable이 eager인 것은 아니며 generator도 iterable입니다.

SYNC PULL

일반 generator

next 요청 때 실행해 다음 yield까지 생산합니다. 교재 ID 생성기는 필요한 만큼만 값을 만들며 MyCollection은 기존 배열을 순회합니다.

ASYNC PULL

async generator

next 결과를 Promise로 받습니다. 교재 사용자 스트림처럼 항목마다 비동기 작업을 기다리고 for await로 소비합니다.

OWNERSHIP

중간 종료·실패의 정리

return과 finally가 어떤 자원을 정리하는지 구현합니다. 소비 속도가 pull이어도 내부 prefetch·buffer·외부 생산 속도는 별도 설계입니다.

도구 이름보다 값의 의미와 생산·소비·정리 책임을 먼저 결정합니다.

위 그림처럼 도구는 값이 하나인지 여럿인지, 지금 있는 값인지 요청할 때 만드는 값인지로 고릅니다. 하나의 값은 그대로 돌려주고, 나중에 한 번 나오는 값은 Promise로, 이미 모인 여러 값은 배열로 다룹니다. 요청할 때마다 하나씩 만드는 값은 제너레이터로, 하나씩 기다려야 하는 값은 비동기 제너레이터로 다루며, 중간에 멈춘 반복을 정리하는 일을 누가 맡는지도 함께 정합니다.

정리

  • 제너레이터 함수는 부르면 본문을 실행하지 않고 객체를 돌려주며, next()를 부를 때마다 다음 yield까지 실행하고 멈춥니다.
  • next의 인수는 멈추어 있던 yield 식의 값이 되고, Generator<Y, R, N>은 내보내는 값과 돌려주는 값, 받아들이는 값의 타입을 적습니다.
  • yield*는 다른 이터러블에 일을 맡기고, for...of와 전개 구문은 return의 값을 항목으로 세지 않습니다.
  • 제너레이터는 요청한 만큼만 값을 만들어 끝없는 시퀀스도 표현할 수 있고, [Symbol.iterator]를 제너레이터로 만들면 직접 만든 이터러블이 됩니다.
  • throw와 return은 멈추어 있는 yield의 자리에서 오류나 반환을 일으키고, 비동기 제너레이터는 for await로 값을 하나씩 기다려 꺼냅니다.

연습 문제

1. 입력 순서를 바꾼 호출

새 simpleGenerator() 객체 g에 g.next(), g.next(false), g.next(true), g.next()를 차례로 부르면, 네 결과와 두 입력값의 로그는 무엇인가요?

해설 보기

결과는 차례로 { value: 1, done: false }, { value: 2, done: false }, { value: '모든 작업 완료!', done: true }, { value: undefined, done: true }입니다. 첫 yield에는 false, 둘째 yield에는 true가 들어가므로, 로그의 입력값도 false, true의 순서입니다. 넘기는 값만 바뀌었을 뿐 내보내는 값과 멈추는 위치는 같습니다.

2. 시작 전의 return

아직 next()를 부르지 않은 새 errorHandlingGenerator() 객체에 return(10)을 먼저 부르면 결과는 무엇이고, 정리 작업은 출력되나요?

해설 보기

결과는 { value: 10, done: true }이고, 정리 작업은 출력되지 않습니다. 본문이 아직 시작되지 않아 try 블록에 들어간 적이 없으므로, 그 finally도 실행할 이유가 없기 때문입니다. 제너레이터 객체를 만들었다는 것과 본문에서 무언가를 시작했다는 것은 다르다는 점을 보여 줍니다.

3. 한 번 쓴 제너레이터 다시 돌기

const gen = combined();로 객체 하나를 만들고 console.log([...gen].length, [...gen].length);를 실행하면 무엇이 출력되나요? new MyCollection([1, 2])을 두 번 펼치면 어떻게 다른가요?

해설 보기

7 0이 출력됩니다. 첫 번째 전개가 gen을 끝까지 꺼냈으므로, 같은 객체를 다시 펼치면 남은 값이 없습니다. MyCollection은 펼칠 때마다 [Symbol.iterator]()가 새 제너레이터를 만들어 주므로, 두 번 펼쳐도 매번 [1, 2]를 얻습니다. 처음부터 다시 돌아야 한다면 제너레이터 객체를 새로 만들어야 합니다.

다음 글 RxJS와 반응형 프로그래밍에서는 값을 당겨 가는 제너레이터와 반대로, 값이 생길 때마다 밀어 보내는 Observable과 그것을 다루는 RxJS를 다룹니다.