JavaScript 제너레이터: 코드의 'Pause' 버튼
Source: Dev.to
Introduction
표준 JavaScript 함수에 익숙하다면 run‑to‑completion 규칙을 알고 있을 것입니다: 일반 함수가 실행을 시작하면 return 문에 도달하거나 코드 블록이 끝날 때까지는 중단될 수 없습니다. 제너레이터는 이 규칙을 깨뜨립니다.
제너레이터는 실행 중간에 일시 정지할 수 있고, 다른 코드를 실행하게 한 뒤, 정확히 멈춘 지점부터 다시 시작하며 모든 변수를 기억합니다. 일반 함수를 끝까지 타야 하는 롤러코스터에 비유한다면, 제너레이터는 넷플릭스 시리즈와 같습니다—잠깐 보고, 일시 정지하고, 다른 일을 한 뒤 같은 지점에서 다시 이어볼 수 있습니다.
Defining a Generator
두 가지 특수 문법이 사용됩니다:
function*– 별표(*)는 JavaScript에 이것이 제너레이터임을 알려줍니다.yield– “일시 정지” 버튼.
function* myGenerator() {
console.log("1. Starting...");
yield "First Pause"; // Pauses here and outputs "First Pause"
console.log("2. Resuming...");
yield "Second Pause"; // Pauses here and outputs "Second Pause"
console.log("3. Finishing!");
return "Done";
}
Creating and Running a Generator
제너레이터 함수를 호출한다고 해서 코드가 실행되는 것이 아니라, 제너레이터 객체(이터레이터)를 반환합니다. 이 객체는 리모컨처럼 동작합니다.
const gen = myGenerator(); // No console logs happen here!
실행을 시작하려면 제너레이터 객체에 .next()를 호출합니다. 각 호출은 다음 yield(또는 return)까지 코드를 실행하고 객체를 반환합니다:
value–yield뒤에 오는 값.done– 제너레이터가 계속될 수 있으면false, 끝났으면true.
console.log(gen.next());
// Output:
// "1. Starting..."
// { value: "First Pause", done: false }
console.log(gen.next());
// Output:
// "2. Resuming..."
// { value: "Second Pause", done: false }
console.log(gen.next());
// Output:
// "3. Finishing!"
// { value: "Done", done: true }
Sending Data Into a Generator
yield는 데이터를 받을 수도 있습니다. .next(value)를 호출하면 전달된 value가 일시 정지된 yield 표현식의 결과가 됩니다.
function* chattyGenerator() {
const name = yield "What is your name?";
const age = yield "How old are you?";
return `Hello ${name}, you are ${age}!`;
}
const gen2 = chattyGenerator();
console.log(gen2.next().value); // "What is your name?"
console.log(gen2.next("John").value); // "How old are you?"
console.log(gen2.next(30).value); // "Hello John, you are 30!"
Lazy (On‑Demand) Data Generation
제너레이터는 메모리를 소모하지 않고 잠재적으로 무한한 시퀀스를 생성하는 데 이상적입니다.
function* idGenerator() {
let id = 1;
while (true) {
yield id;
id++;
}
}
const ids = idGenerator();
console.log(ids.next().value); // 1
console.log(ids.next().value); // 2
console.log(ids.next().value); // 3
Delegating to Another Generator (yield*)
yield*는 제어권을 다른 제너레이터에게 넘겨주어 구성(composition)을 가능하게 합니다.
function* guestDJ() {
yield "Song A";
yield "Song B";
}
function* mainDJ() {
yield "Intro";
yield* guestDJ(); // Hand over control until guestDJ finishes
yield "Outro";
}
for (const song of mainDJ()) {
console.log(song);
}
// Output:
// Intro
// Song A
// Song B
// Outro
Generators and Asynchronous Code
async/await가 등장하기 전, Redux‑Saga 같은 라이브러리는 제너레이터를 사용해 비동기 흐름을 처리했습니다. 제너레이터가 Promise를 yield하면, 러너는 해당 프로미스가 해결될 때까지 실행을 일시 정지할 수 있습니다.
function* fetchUser() {
// Yield the promise; the runner waits for it to resolve.
const user = yield fetch('/api/user');
// The resolved value is passed back into the generator.
console.log(user.name);
}
개념적으로 function*는 async function과 비슷하고, yield는 await와 같은 역할을 합니다.
Summary – Generator Cheatsheet
| Syntax | Meaning |
|---|---|
function* | 제너레이터 함수를 선언 |
yield value | 실행을 일시 정지하고 value를 출력 |
const x = yield | 일시 정지하고 .next() 로 전달된 데이터를 기다림 |
generator.next() | 다음 yield(또는 return)까지 코드를 실행 |
yield* otherGenerator() | 다른 제너레이터에 제어권 위임 |
제너레이터는 코드 흐름을 수동으로 제어할 수 있게 해줍니다. async/await가 간단한 데이터 가져오기에서는 대부분을 대체했지만, 제너레이터는 복잡한 흐름, 무한 데이터 스트림, 상태 머신 구현 등에 여전히 강력한 도구입니다.