본문 바로가기
IT/javascript

[JS] 제너레이터 함수 (generator function)

by 내일은교양왕 2024. 7. 7.

개념

함수의 실행을 중단하고 재개할 수 있는 특별한 유형의 함수

`function*` 키워드로 정의되고, 내부에서 `yield` 키워드를 사용하여 값을 반환하고 함수의 실행을 일시 중지할 수 있다.

 

function* simpleGenerator() {
    yield 1;
    yield 2;
    yield 3;
}

const gen = simpleGenerator();

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 }

 

`done` 속성

`next()` 메소드는  value와 done 속성을 가지고 있는 객체다. value는 yield에 반환된 값이고, `done`의 속성을 가지고 있다. 

 

무한시퀀스

function* infiniteGenerator() {
    let i = 0;
    while (true) {
        yield i++;
    }
}

const gen = infiniteGenerator();
console.log(gen.next().value); // 0
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
// 계속해서 호출 가능

 

제너레이터와 for...of 루프

next()를 사용하지 않고 일반 루프 처럼 돌아가는 것이 주목해야할 점

function* simpleGenerator() {
    yield 1;
    yield 2;
    yield 3;
}

for (const value of simpleGenerator()) {
    console.log(value); // 1, 2, 3
}

 

상태유지

상태를 유지하면서 중단 및 재개할 수 있다. 

상태 기반 논리가 필요한 경우 유용

function* statefulGenerator() {
    let state = 0;
    while (true) {
        state = yield state;
        state += 1;
    }
}

const gen = statefulGenerator();
console.log(gen.next().value); // 0
console.log(gen.next(10).value); // 11
console.log(gen.next(20).value); // 21

 

비동기 작업을 처리하거나 복잡한 데이터 스트림을 다루는데 유용.

즉 async / async와 결합하여 사용될 때 큰 장점을 가진다.

 

비동기 작업을 순차적으로 실행

function asyncOperation1() {
    return new Promise((resolve, reject) => setTimeout(() => resolve({a: 'a'}), 1000))
}

function asyncOperation2(result) {
    return new Promise((resolve, reject) => setTimeout(() => resolve({...result, b: 'b'}), 1000))
}

function asyncOperation3(result) {
    return new Promise((resolve, reject) => setTimeout(() => resolve({...result, c: 'c'}), 1000))
}

function* asyncTaskGenerator() {
    const result1 = yield asyncOperation1();
    const result2 = yield asyncOperation2(result1);
    const result3 = yield asyncOperation3(result2);
    return result3
}

function run(generator) {
    const iterator = generator();

    function handle(result) {
        console.log('handle', result)
        if (result.done) return result.value;
        return Promise.resolve(result.value).then(res => handle(iterator.next(res)));
    }

    return handle(iterator.next());
}

/**
 * handle { value: Promise { <pending> }, done: false }
 * handle { value: Promise { <pending> }, done: false }
 * handle { value: Promise { <pending> }, done: false }
 * handle { value: { a: 'a', b: 'b', c: 'c' }, done: true }
 * final result { a: 'a', b: 'b', c: 'c' }
 */
run(asyncTaskGenerator).then(result => {
    console.log('final result', result);
});

 

스트림에서 제너레이터 함수 사용하는 예

const fs = require('fs');
const readline = require('readline');

async function* readLines(filePath) {
    const fileStream = fs.createReadStream(filePath);
    const rl = readline.createInterface({
        input: fileStream,
        crlfDelay: Infinity
    }); //파일을 한 줄씩 읽을 수 있는 인터페이스를 생성합니다.

    for await (const line of rl) {
        // 파일의 각 줄을 읽고 yield 키워드를 사용하여 각 줄을 반환합니다.
        console.log('readLines',line)
        yield line;
    }
}

async function processFile(filePath) {
    const lineGenerator = readLines(filePath);

    for await (const line of lineGenerator) {
        // 여기서 각 줄을 처리할 수 있습니다.
        console.log('processFile',line)
    }
}

/**
 * readLines hello world
 * processFile hello world
 * readLines it's rainy now
 * processFile it's rainy now
 */
processFile('./lowercase.txt');