티스토리 뷰

JavaScript

JS - generator 함수와 yield

김관장 2022. 7. 12. 18:05

회사에서 redux-saga 상태 관리 라이브러리로 개발한 프로젝트가 있는데 generator 함수와 yield 키워드를 사용해서 개발을 했었습니다. 당시에는 개발 기한이 짧아 잘 이해하지 못하고 사용했었는데, 블로그에 글을 쓰다가 generator 함수와 yield 키워드와 관련된 내용이 나와서 이번에 공부하고 포스팅합니다!!

 

1. generator 함수와 yield


  • ✅  function* 키워드로 함수를 정의(generator)하고 이 함수에 포함되어 있는 property 중에 next()를 호출하여, 함수 내에서 중지했던 yield 부분부터 재개합니다.

   1-1. generator 함수

 

  • function 뒤에 astrok(*)를 붙여서 generator 함수를 정의합니다.
  • 제네레이터 함수는 일반 함수와 같이 함수의 코드 블록을 한 번에 실행하는 것이 아닌 함수 코드 블록의 실행을 일시 정지했다가 필요한 시점에 재시작할 수 있는 함수입니다.
  • 마치 함수가 실행 중에 특정 부분에서 일시 정지된 것처럼 동작함으로써, 비동기 처리를 하도록 만들 수가 있습니다.

   1-2. yield

 

  • generator 함수 안에 yield 키워드를 사용한 곳들을 기준으로 코드를 일시 정지합니다.

   1-3. next

 

  • next 함수 호출하면 멈췄던 부분부터 실행을 이어서 하고 만약 다음 yield가 있으면 해당 부분에서 일시 정지합니다.
  • generator 함수에서 반환한 데이터를 generator라고 부르는데, generator의 next함수를 실행하면 {value, done} 타입의 객체 데이터를 반환합니다.
  • next()를 실행하는 이유는 generator는 반복적인 iterator를 사용하는 구조이기 때문입니다.

* iterator(반복자) : 객체 지향 프로그래밍에서 배열이나 그와 유사한 자료구조의 내부의 요소를 순환하는 객체

 

function* call() {
    console.log('실행 1');
    yield 'type1';
    console.log('실행 2');
    yield 'type2';
    console.log('실행 3');
    yield 'type3';
}

// 방법 1
let calling = call();
console.log(calling.next());
console.log(calling.next());
console.log(calling.next());
console.log(calling.next());
//


// 방법 2
for(let result of call()){
    console.log(result);
}
//

1. 제너레이터 함수 call을 정의하고, 호출하여 사용해봤습니다.

 

2. next 함수를 통해 다음 함수를 실행하면서 호출해본 결과  yield 문을 만날 때까지 실행되었다 멈추는 것을 확인할 수 있습니다.

 

3. value : yield로 반환했던 값, done : 함수의 종료 여부 } 형식으로 리턴 값이 반환되는 것을 확인할 수 있습니다. 

 

 

* generator는 반복적인 iterator를 사용하는 구조이기 때문에 for문 활용이 가능합니다.

 

심화 내용이 궁금하다면 Click!!

더보기

sub 1) 제너레이터 종료하기

 

  • return 문
    • return이 호출되면 value에는 return의 인자가 할당되고, done은 true가 됩니다.
function* increment() {
    let i = 0;
    while (true) {
        yield i++;
    }
}

const withReturn = increment();
console.log(withReturn.next()); // {value: 0, done: false}
console.log(withReturn.next()); // {value: 1, done: false}
console.log(withReturn.next()); // {value: 2, done: false}
console.log(withReturn.next()); // {value: 3, done: false}
console.log(withReturn.return(100)); //{value: 100, done: true}

 

  • return문 + try / finally
    • return이 호출되었을 때 제너레이터 함수의 코드가 try / finally 안에 있으면, 종료되지 않습니다. return 이후 finally 블록의 yield 표현식이 실행되며, 종료될 때는 reuturn에 전달된 값으로 종료됩니다. 
function* increment() {
    yield 1;

    try {
        yield 2;
        yield 3;
        yield 4;
    } finally {
        yield 5;
        yield 6;
    }

    yield 7;
}

const withReturn = increment();
console.log(withReturn.next()); // {value: 1, done: false}
console.log(withReturn.next()); // {value: 2, done: false} => 이 시점에 try/finally 문으로 들어옴
console.log(withReturn.return(100)); // {value: 5, done: false}
console.log(withReturn.next()); // {value: 6, done: false}
console.log(withReturn.next()); // {value: 100, done: true}

 

sub 2) yield*

 

  • yield에 *를 붙여 사용하면 yield* 에 표현된 이터러블 객체를 순회하며 결과값을 반환합니다.
function* increment() {

    let a = 10;
    yield a;
    yield* [1, 2, 3].map((dr) => dr * a);

    let b = 20;
    yield b;
    yield* [4, 5, 6].map((dr) => dr * b);
}

for (let result of increment()) {
    console.log(result);
}

/* 호출결과
    10
    10
    20
    30
    20
    80
    100
    120
 */

2. generator + yield     vs     async + await


  • ✅  generator 함수는 ES2015에 나와 일시 중지했다가 필요한 시점에 재시작하는 특징을 통해 비동기 처리에 유용하게 사용되었나, ES2017에 async/await을 이용하여 비동기 처리가 가능해짐에 따라 존재감이 낮아지고 있습니다.

  • generator + yield의 경우 generator의 리턴 값이 iterator로, yield에서 작업이 중단되면 next()를 통해 다음 작업으로 넘어가는 불편함이 있습니다.
  • async + await의 경우 await에 promise가 오면 resolve가 반환될 때까지 기다렸다가 다음 작업으로 넘어가기 때문에 별도의 처리를 해 줄 필요가 없습니다.

=> 비동기 코드를 기다리는 즉, 동기식으로 동작하는 것처럼 보이는 방식으로 작성하기 위한 훌륭한 방법은 async + await입니다! 하지만 한 번에 여러 개의 promise를 기다릴 수 없기 때문에, 여러 개의 promise를 동시에 처리를 하기 위해서는 promise 함수를 사용해야 합니다. 

3. redux-saga / effects 함수에서의 generator 함수와 yield 사용


  • ✅  redux는 ES6의 generator 함수를 기반으로 구현되었습니다. function* 키워드로 함수를 정의하고, 이 함수를 호출하는 행위는 redux-saga/effects에 위임시키고, redux에서 제공하는 action을 사용하여 함수 내 yield 문을 차례로 실행시키는 것입니다. 이때 redux에서 함수 내의 yield를 알아서 차례로 실행시켜주기 때문에 next()를 계속해서 호출할 필요는 없습니다.

   3-1. redux-saga / effects 함수

 

  • redux-saga / effects 함수로는 all, call, put, takeLatest 등이 있습니다.

    all : 비동기 처리가 필요한 함수들을 배열 행태로 넣어서 동시에 병행 처리를 수행하는 함수
    call :  비동기 처리가 필요한 함수들을 실행하는 함수 (주로 ajax, http, delay 등의 함수를 처리)
    put : action을 dispatch 하는 함수
    takeLatest : 기존에 진행 중이던 작업이 있다면 취소하고 가장 마지막으로 실행된 작업을 수행하는 함수

  • redux-saga / effects 함수들을 사용하게 되면, function* 함수를 호출할 때 next()를 통해 호출하는 것이 아닌, action이라는 것을 통해 호출합니다. action을 통해 function*을 한 번만 호출하면 내부에 있는 여러 yield 들을 next() 호출 처리 없이 차례로 호출을 해줍니다. 이러한 점이 비동기 처리에 대한 코드를 보다 쉽체 처리할 수 있게 해 줍니다.

 

🤔  정리 !

  • generator함수는 function* 키워드로 정의, yield 키워드를 만나면 일시 정지, next()를 호출하면 일시 정지된 부분부터 재개 ] 이는 마치 함수가 특정 부분에서 일시 정지된 것처럼 동작하므로 비동기 처리를 하도록 만들 수가 있다!
  • 비동기 처리를 하는 더 좋은 방법은 async + await을 사용하는 것!
  • redux-saga/effects 함수에서는 action을 통해 generator 함수를 호출하면 함수 내 yield 부분 들을 자동으로 차례로 호출! 

 

 

🔗 참고한 

 

javascript - yield (비동기 처리

yield 비동기 처리 (Asynchronous processing model) javascipt에서 지원하는 함수 중에 yield라는 함수가 있다. 글쓴이는 javascript를 잘 모르는 상태에서 react를 개발을 시작했었고, 처음으로 yield라는 함수..

caileb.tistory.com

'JavaScript' 카테고리의 다른 글

시맨틱 웹(Semantic Web) ?  (2) 2022.07.16
JS - 구조 분해 할당 문법 ?  (0) 2022.07.15
JS - JSON ?  (0) 2022.07.09
JS - 호이스팅 (Hoisting) ?  (0) 2022.07.05
JS - Lodash ?  (3) 2022.07.04
댓글