본문 바로가기
기술의기록

JavaScript yield와 yield* 이해하기

by Jeremy Winchester 2026. 1. 26.
반응형

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*: 이터러블 전체를 위임
  • 제너레이터: 중간에 멈췄다 다시 시작 가능한 함수
  • 활용: 무한 시퀀스, 대용량 데이터 처리, 비동기 로직에 최적!
반응형