JavaScript를 공부하다 보면 yield라는 키워드를 마주하게 되죠. 처음엔 "이게 뭐지?" 싶다가도, 알고 나면 정말 강력한 도구라는 걸 알게 됩니다! 🚀 특히 비동기 처리나 대용량 데이터를 다룰 때 yield를 제대로 활용하면 코드가 훨씬 깔끔해지고 효율적으로 변하거든요.
오늘은 많은 분들이 헷갈려하시는 yield와 yield* 의 차이점을 실전 예제와 함께 완벽하게 정리해드릴게요. 이 글 하나면 제너레이터 함수의 모든 것을 이해하실 수 있을 거예요! 💪

🎯 제너레이터 함수란 무엇인가요?
일반 함수는 한 번 실행하면 끝까지 달려가잖아요? 그런데 제너레이터 함수는 중간에 멈췄다가 다시 시작할 수 있는 특별한 함수예요. 마치 동영상 일시정지 버튼을 누르는 것처럼요!
제너레이터 함수는 function* 이렇게 별표(*)를 붙여서 선언합니다:
function* myGenerator() {
yield 1;
yield 2;
yield 3;
}
const gen = myGenerator();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }
핵심 포인트:
- function*로 제너레이터 함수 선언
- yield로 실행을 일시정지하고 값 반환
- next() 메서드로 다음 단계 실행
- {value, done} 객체 형태로 결과 반환
💡 yield - 기본 중의 기본
yield 키워드는 제너레이터 함수의 핵심이에요. 함수 실행을 일시정지하고, 값을 외부로 반환하는 역할을 합니다.
yield의 동작 원리
function* countUp() {
console.log('시작!');
yield 1;
console.log('첫 번째 yield 통과');
yield 2;
console.log('두 번째 yield 통과');
yield 3;
console.log('완료!');
}
const counter = countUp();
console.log(counter.next().value); // "시작!" 출력 후 1 반환
console.log(counter.next().value); // "첫 번째 yield 통과" 출력 후 2 반환
console.log(counter.next().value); // "두 번째 yield 통과" 출력 후 3 반환
console.log(counter.next().value); // "완료!" 출력 후 undefined 반환
보시다시피 yield를 만날 때마다 함수가 멈추고, next()를 호출하면 그 다음 yield까지 실행되는 거죠!
실전 활용: 무한 시퀀스 생성
function* infiniteNumber() {
let num = 0;
while (true) {
yield num++;
}
}
const numbers = infiniteNumber();
console.log(numbers.next().value); // 0
console.log(numbers.next().value); // 1
console.log(numbers.next().value); // 2
// 필요한 만큼만 생성!
왜 유용할까요?
무한 루프를 돌려도 메모리를 다 차지하지 않아요. 필요할 때만 값을 생성하니까 메모리 효율이 엄청 좋죠! 💪
⭐ yield* - 제너레이터의 숨은 고수
자, 이제 본격적으로 yield*를 알아볼까요? yield*는 다른 제너레이터나 이터러블 객체에게 실행 권한을 위임하는 키워드예요.
yield와 yield*의 결정적 차이
// yield 사용
function* gen1() {
yield [1, 2, 3];
}
const g1 = gen1();
console.log(g1.next()); // { value: [1, 2, 3], done: false }
// yield* 사용
function* gen2() {
yield* [1, 2, 3];
}
const g2 = gen2();
console.log(g2.next()); // { value: 1, done: false }
console.log(g2.next()); // { value: 2, done: false }
console.log(g2.next()); // { value: 3, done: false }
차이가 보이시나요?
- yield는 배열 자체를 하나의 값으로 반환
- yield*는 배열의 각 요소를 하나씩 반환!
제너레이터 함수 체이닝하기
yield*의 진짜 강력함은 제너레이터끼리 연결할 때 나타나요:
function* numbers() {
yield 1;
yield 2;
}
function* letters() {
yield 'A';
yield 'B';
}
function* combined() {
yield* numbers();
yield* letters();
yield '끝!';
}
const result = combined();
console.log(result.next().value); // 1
console.log(result.next().value); // 2
console.log(result.next().value); // A
console.log(result.next().value); // B
console.log(result.next().value); // 끝!
엄청 깔끔하죠? 🎨 코드를 모듈화하고 재사용하기 정말 좋아요!
🔥 실전 예제: 실무에서 이렇게 쓰세요!
1. 대용량 데이터 처리
function* dataFetcher(data) {
for (let item of data) {
// 데이터 가공 로직
const processed = processData(item);
yield processed;
}
}
// 1000만 개 데이터도 한 번에 하나씩 처리!
const bigData = getBigData();
const processor = dataFetcher(bigData);
for (let result of processor) {
console.log(result); // 메모리 걱정 없이 처리 가능
}
2. 비동기 작업 순차 처리
function* taskRunner() {
const user = yield fetchUser();
const posts = yield fetchPosts(user.id);
const comments = yield fetchComments(posts[0].id);
return comments;
}
function run(generator) {
const gen = generator();
function handle(result) {
if (result.done) return result.value;
return result.value.then(data => {
return handle(gen.next(data));
});
}
return handle(gen.next());
}
run(taskRunner).then(data => console.log(data));
이런 패턴이 바로 async/await의 기초가 되었답니다! 😮
3. 무한 스크롤 구현
function* infiniteScroll(itemsPerPage) {
let page = 1;
while (true) {
const items = yield fetchItems(page, itemsPerPage);
page++;
yield items;
}
}
const scroll = infiniteScroll(20);
// 스크롤할 때마다
scroll.next(); // 다음 20개 데이터 가져오기
📊 yield vs yield* 비교표
구분 yield yield*
| 역할 | 값 하나를 반환하고 일시정지 | 이터러블의 모든 값을 순회하며 반환 |
| 반환 타입 | 단일 값 | 이터러블의 각 요소 |
| 사용 예 | yield 10 | yield* [1,2,3] |
| 제너레이터 위임 | 불가능 | 가능 |
| 주 용도 | 개별 값 생성 | 여러 값 위임, 제너레이터 체이닝 |
🎓 알아두면 좋은 팁!
1. for...of와 함께 사용하기
function* range(start, end) {
for (let i = start; i <= end; i++) {
yield i;
}
}
for (let num of range(1, 5)) {
console.log(num); // 1, 2, 3, 4, 5
}
주의할 점: return 값은 for...of에서 출력되지 않아요! done: true일 때의 값은 무시되거든요.
2. 양방향 통신
function* dialogue() {
const name = yield '이름이 뭐예요?';
const age = yield `${name}님, 나이가 어떻게 되세요?`;
yield `${name}님은 ${age}살이시군요!`;
}
const chat = dialogue();
console.log(chat.next().value); // 이름이 뭐예요?
console.log(chat.next('철수').value); // 철수님, 나이가 어떻게 되세요?
console.log(chat.next(25).value); // 철수님은 25살이시군요!
next()에 값을 전달할 수 있다는 거! 양방향 통신이 가능해져요! 🔄
3. 에러 처리
function* safeGenerator() {
try {
yield 1;
yield 2;
yield 3;
} catch (e) {
console.log('에러 발생:', e);
}
}
const gen = safeGenerator();
gen.next();
gen.throw(new Error('문제 발생!')); // 에러 발생: Error: 문제 발생!
🚀 언제 사용하면 좋을까요?
yield를 쓰면 좋은 경우
✅ 무한 시퀀스를 생성할 때
✅ 대용량 데이터를 조금씩 처리할 때
✅ 지연 평가(lazy evaluation)가 필요할 때
✅ 상태를 유지하면서 값을 생성할 때
yield*를 쓰면 좋은 경우
✅ 다른 제너레이터를 조합할 때
✅ 이터러블 객체의 모든 값을 순회할 때
✅ 재귀적인 제너레이터 구조를 만들 때
✅ 코드를 모듈화하고 재사용할 때
⚠️ 흔한 실수와 해결방법
실수 1: 일반 함수에서 yield 사용
// ❌ 틀린 예시
function normalFunction() {
yield 1; // SyntaxError!
}
// ✅ 올바른 예시
function* generatorFunction() {
yield 1;
}
실수 2: yield*를 객체에 사용
// ❌ 틀린 예시
function* wrongUsage() {
yield* { a: 1, b: 2 }; // TypeError! 객체는 이터러블이 아님
}
// ✅ 올바른 예시
function* correctUsage() {
yield* Object.values({ a: 1, b: 2 }); // [1, 2]
}
실수 3: return과 yield 혼동
function* mixedFunction() {
yield 1;
yield 2;
return 3; // done: true가 되면서 종료
yield 4; // 실행되지 않음!
}
for (let value of mixedFunction()) {
console.log(value); // 1, 2만 출력 (3은 출력 안됨!)
}
🌟 실무 활용 사례
Redux-Saga에서의 활용
function* fetchUserSaga(action) {
try {
const user = yield call(api.fetchUser, action.userId);
yield put({ type: 'FETCH_USER_SUCCESS', user });
} catch (e) {
yield put({ type: 'FETCH_USER_FAILURE', message: e.message });
}
}
function* rootSaga() {
yield* [
takeEvery('FETCH_USER_REQUEST', fetchUserSaga),
takeEvery('FETCH_POSTS_REQUEST', fetchPostsSaga),
];
}
Redux-Saga는 제너레이터를 활용한 대표적인 라이브러리예요. 비동기 로직을 동기적으로 작성할 수 있어서 테스트하기도 훨씬 쉽죠!
커스텀 이터레이터 만들기
const fibonacci = {
*[Symbol.iterator]() {
let [prev, curr] = [0, 1];
while (true) {
yield curr;
[prev, curr] = [curr, prev + curr];
}
}
}
// 피보나치 수열의 처음 10개만 가져오기
const first10 = [];
for (let num of fibonacci) {
if (first10.length >= 10) break;
first10.push(num);
}
console.log(first10); // [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
💬 마무리하며
자, 여기까지 JavaScript의 yield와 yield*에 대해 알아봤어요! 처음엔 낯설 수 있지만, 제너레이터는 정말 강력한 기능이랍니다. 특히 메모리 효율이 중요한 상황이나 비동기 처리를 깔끔하게 다루고 싶을 때 정말 유용해요! 🎯
핵심만 정리하면:
- yield: 값 하나 반환하고 일시정지
- yield*: 이터러블 전체를 위임
- 제너레이터: 중간에 멈췄다 다시 시작 가능한 함수
- 활용: 무한 시퀀스, 대용량 데이터 처리, 비동기 로직에 최적!
'기술의기록' 카테고리의 다른 글
| 실전 프로젝트로 배우는 oh-my-claude 활용법: 3가지 케이스 스터디 (0) | 2026.01.29 |
|---|---|
| Claude Code가 마법처럼 똑똑해지는 비밀: oh-my-claude-sisyphus 완벽 가이드 (1) | 2026.01.26 |
| Server Component와 Server Function는 왜 써야하는가? (1) | 2026.01.23 |
| Codingbuddy MCP 완벽 가이드: AI 코딩 도구 통합 규칙 시스템 설정법 (0) | 2025.12.29 |
| n8n 원격코드실행 취약점 CVE-2025-68613 긴급 패치 필수! 완벽 대응 가이드 (0) | 2025.12.29 |