ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Js] 자바스크립트, Generator 함수에 대하여
    Frontend 2020. 3. 29. 18:01

    공부해야지 해야지 하고 맨날 뒤로 미루다가 오늘 드디어 Generator에 대해서 정리를 해보려고 한다.

     

    Generator는 실행을 잠시 멈췄다가 나중에 다시 접근할 수 있는 아주 특이한 형태의 함수이다.

     

    Generator 함수는 나중에 다시 접근하기 위해서 context(즉 변수값)를 저장된 상태로 남겨둔다.

    Generator는 주로 Promise와 결합하여 사용되며, Callback 지옥같은 비동기 프로그래밍의 문제점들을 많이 완화시켜준다.

     

     

    일반 함수

    예를 들어서 아래와 같은 간단한 함수를 살펴보자.

     

    function sayHi() {
      // statements
      return 'hi';
      return 'hello'; // was never executed
    }

     

    위 함수를 실행시키면 첫 번째 return문인 return 'hi'를 만나자마자 함수의 실행이 종료될 것이다. 따라서 return 'hello'는 절대 실행되지 않는다. 그렇다면 Generator 함수는 어떨까?

     

     

    Generator 함수

    function* sayHiGenerator(params) {
      yield 'hello';
      yield 'world';
      // statements
      return 'hi';
    }
    
    const resultGenerator = sayHiGenerator();
    console.log(resultGenerator); // { [Iterator] } -> generator object

     

    Generator 함수는 함수 키워드 뒤에 * 문자가 붙는 형태이다. 그리고 함수 내부에 yield라는 키워드가 사용된다.

    Generator 함수는 호출되더라도 함수의 body를 즉시 실행하지 않는다. 대신 함수의 iterator 객체를 반환한다.

     

    console.log(resultGenerator.next()); // { value: 'hello', done: false } -> still in suspender state
    console.log(resultGenerator.next()); // { value: 'world', done: false } -> still in suspender state
    console.log(resultGenerator.next()); // { value: 'hi', done: true } -> finished
    console.log(resultGenerator.next()); // { value: undefined, done: true } -> nothing to return here
    

     

     

    그리고 반환된 iterator 객체의 next() 메소드가 호출되면 Generator 함수의 body가 yield expression이 나타날때까지 실행된다.

     

     

    next() 메소드

    next() 메소드는 다음과 같은 형태의 객체를 리턴한다.

    {
      value: 'hello', // yielded value
      done: false
    }

     

    위 객체에서 value는 yield expression이 반환할 값(즉, yielded value)이고,

    done은 Generator 함수가 가장 마지막 값을 yield했는지 여부를 boolean 값으로 나타낸 것을 의미한다.

     

     

    Generator 함수 내에 return statement가 있다면, 그 statement를 만나는 즉시 Generator 함수가 종료하게 된다.

    (따라서 done 속성이 true가 되며, 리턴된 값이 value가 된다.)

     

    만약에 Generator 내부에서 에러가 발생한다면, Generator의 body 안에서 catch되어 핸들링되지 않는 이상 Generator 함수가 종료된다.

     

    Generator가 존료되고 나면 next() 메소드 호출이 더 이상 불가능하며 다음 객체만 반환할 것이다.

     

    {
      value: undefined,
      done: true
    }

     

     

    Generator with for-of

    만약에 sayHiGenerator 함수를 실행해서 반환받은 iterator 객체를 for-of 문으로 iterate하게 되면

    next 함수로 반환되는 각 객체의 value값을 순회할 수 있다.

     

    const resultGeneratorForOf = sayHiGenerator();
    
    for (const val of resultGeneratorForOf) {
      console.log(val); // hello world
    }

     

     

    next() 메소드에 인자를 넣어 호출하면?

    next() 메소드에 특정 값을 인자로 넣어 호출하면, 진행을 멈췄던 위치의 yield expression을 인자값으로 교체하고 그 위치에서 다시 실행한다. 다음 예제 코드를 살펴보자.

     

    function* newGenerator() {
      yield 'something'; // yeild keyword는 우리가 바깥에서 value를 넣는 것을 허용함
    
      const final = yield 'give me value';
      return final; // customize this return value outside
    }
    
    const newGeneratorIt = newGenerator();
    
    console.log(newGeneratorIt.next()); // { value: 'something', done: false }
    console.log(newGeneratorIt.next()); // { value: 'give me value', done: false }
    console.log(newGeneratorIt.next('custom value')); // { value: undefined -> 'custom value', done: true }

     

    첫 번째 next() 함수를 호출하면 yield 'something'에서 종료하여 'something'을 value로 리턴한다.

    두 번째 next() 함수를 호출하면 yield 'give me value'에서 종료하며 'give me value'를 리턴한다.

     

    만약 여기서 next() 함수를 다시 호출하면 이번엔 value가 undefined가 된다.

    왜냐면 next 함수를 호출할때 인자로 넣은 값이 없어 final에 어떤 값도 할당되지 않았기 때문이다.

     

    그러나 'custom value'라는 string을 인자로 넣어 실행하면 value에 'custom value'가 들어간다.

     

     

    function* testGenerator() {
      yield 1;
    
      const val = yield 2;
      const final = yield 3;
      return val; // customize this return value outside
    }
    
    const testGeneratorIt = testGenerator();
    
    console.log(testGeneratorIt.next()); // { value: 1, done: false }
    console.log(testGeneratorIt.next()); // { value: 2, done: false }
    console.log(testGeneratorIt.next(100)); // { value: 3, done: false }
    console.log(testGeneratorIt.next()); // { value: undefined -> 100, done: true }

     

    위 함수를 살펴보면 3번째 next를 호출했을때 100을 인자로 넣어주고 있다.

    그러면 4번째로 next() 호출 시 value에 100이 든 객체가 리턴된다.

    그 이유는 Generator가 final이 아닌 val을 리턴하고 있기 때문이다.

     

    즉, 첫번째 호출 시 yield 1을 만나 value가 1인 객체가 리턴된다.

    그 다음 호출 시 yield 2를 만나 value가 2인 객체가 리턴된다.

    그 다음, yield 3을 만나 value가 3인 객체가 리턴되며, 여기서 인자로 들어간 100이 val에 할당된다.

    그 다음 val을 리턴하므로 value가 100인 객체가 리턴된다.

     

     

    function* testGenerator() {
      yield 1;
    
      const val = yield 2;
      const final = yield 3;
      return final; // customize this return value outside
    }
    
    const testGeneratorIt = testGenerator();
    
    console.log(testGeneratorIt.next()); // { value: 1, done: false }
    console.log(testGeneratorIt.next()); // { value: 2, done: false }
    console.log(testGeneratorIt.next()); // { value: 3, done: false }
    console.log(testGeneratorIt.next(100)); // { value: undefined -> 100, done: true }

     

    만약 final을 return한다면 위와 같이 4번째로 next()를 호출할때 넣어준 인자값이 리턴된다.

     

     

    function* gen() {
      while(true) {
        var value = yield null;
        console.log(value);
      }
    }
    
    var g = gen();
    console.log('g.next(1)', g.next(1));
    console.log('g.next(2)', g.next(2));

     

    위 함수를 실행하면 다음과 같은 결과물이 나온다.

    처음에 g.next(1)을 실행하면 yield null을 만나 value가 null인 객체를 리턴한다.

     

    이 때 1이 콘솔에 찍히지 않는 이유는 1이 들어간 시점에는 Generator가 아직 아무런 값도 yield하지 않았기 때문이다.

    따라서 yield null을 한 후, 다음 next(2) 메소드 호출 시 인자로 들어간 2를 콘솔창에서 출력한다.

    그리고 value가 null인 객체를 다시 반환하게 된다.

     

     

     

     

    Reference:

    https://www.youtube.com/watch?v=b6UdHGJNqTw&list=PLMV09mSPNaQlWvqEwF6TfHM-CVM6lXv39&index=3&t=0s

     

    반응형

    COMMENT