DEV Community

Steve Sung
Steve Sung

Posted on

[JS] 비동기프로그래밍 - generator, iterator, async/await

Iterator

  • 배열이나 유사한 자료구조 내부를 순회하는 객체이다
  • iterable, iterator 두가지 iterable protocol이 존재

[Iterable]

  • 객체 멤버를 반복할 수 있는 객체
  • 자바스크립트에서 객체가 iterable 하기 위해서는 [@@iterator] 메소드 구현 필요, object property에 Symbol.iterator를 추가해야 한다.
        const iterable = new Object();

        // 객체는 오직 하나의 Symbol.iterator를 가질 수 있다

        iterable[Symbol.iterator] = function* () {
          yield 1;
          yield 2;
          yield 3;
        };

        console.log([...iterable]); // 1 2 3
        for(var value of iterable) {
            console.log(value); // 1 2 3
        }
  • Array, String, Map, Set, TypedArray가 iterable을 기본적으로 가지고 있다.

[Iterator]

  • 객체를 next() 메서드로 순환 할 수 있는 객체
  • next()메소드 규칙
    • arguments가 없다
    • next 메소드의 반환자는 done: boolean , value:any 를 포함하는 object 를 반환해야한다
    • next 메소드의 반복이 끝날때 done 은 true 를 반환해야 한다 그전에는 false
    // iterator를 반환하는 함수들

    var arr = ['1', '2', '3'];
    console.log([...arr.entries()]);
    // [[0, "1"], [1, "2"], [2, "3"]];
    console.log([...arr.keys()]);
    // [0, 1, 2];
    console.log([...arr.values()]);
    // ["1", "2", "3"];

    var map = new Map([[1, '1'],[2, '2'],[3, '3']])
    console.log(map.entries());
    // MapIterator {1 => "1", 2 => "2", 3 => "3"}
    console.log(map.keys());
    // MapIterator {1, 2, 3}
    console.log(map.values());
    // MapIterator {"1", "2", "3"}

    var set = new Set([1, 2, 3]);
    console.log(set.entries());
    // SetIterator {1, 2, 3}
    console.log(set.keys());
    // SetIterator {1, 2, 3}
    console.log(set.values());
    // SetIterator {1, 2, 3}

    var string = '1,2,3';
    console.log([...string.matchAll(/[\d+]/g)]; // RegExpStringIterator
    // [
    // ["1", index: 0, input: "1,2,3", groups: undefined],
    // ["2", index: 2, input: "1,2,3", groups: undefined],
    // ["3", index: 4, input: "1,2,3", groups: undefined]
    // ]

[iterable 과 iterator의 차이]

  • iterable?
    • iterable한 객체는 iterator를 반환하는 [Symbol.iterator] 프로퍼티를 가진 객체이다.
      • Symbol.iterator는 주어진 객체의 iterator를 반환하는 함수를 명시한다.
    • 정리
      1. 순회 할수 있는 데이터를 가지고 있어야한다.
      2. 전역 “well-known” symbol 인Symbol.iterator 을 메서드로 가지고 있어야한다. 또한 이 메서드는 #3 , #6 에 따라 구현되어야 한다.
      3. Symbol.iterator 메서드는 “iterator” 객체를 반환해야합니다
      4. “iterator” 객체는 반드시 next 라고 하는 메서드를 가져야합니다.
      5. next 에는 #1에 저장되어있는 데이터에 접근 할 수 있어야합니다.
      6. “iterator” 객체인 iteratorObj를 iteratorObj.next()하면 {value:<stored data},done:false} #1 데이터가 추출 되며 전부다 순회했을 경우 {done:true} 가 반환되도록 한다.
  • iterator?
    • 이터레이터는 반복을 위해 설계된, 특별한 인터페이스를 가진 객체이다.
    • 이터레이터 객체는 next()를 가진다.
      • 연산의 결과로 객체를 반환한다. { value: value, done: boolean }

[spread 문법]

  • spread 문법을 이용하면 iterable 객체를 해체할 수 있다

generator

[generator는 쓰레드가 아니다]

  • 쓰레드를 지원하는 언어는 여러 코드를 동시에 실행시킨다
  • 제너레이터 코드는 제너레이터를 호출하는 코드와 같은 쓰레드에서 실행된다
  • 동시에 실행되는 경우는 없고 yield구문에 의해서만 실행을 멈춘다

[generator는 이터레이터다]

  • Symbol.iterator(), . next() 두가지 메소드 구현만으로도 자기만의 이터레이터를 만들수 있다.
    • 객체가 이터러블 하기위해서는 Symbol.iterator 메서드를 추가해야한다
    • next() 메서드를 통해 하나씩 출력한다
        const iterableObj = {
          [Symbol.iterator]() {  
            let step = 0;
            return {
              next() {
                step++;
                if (step === 1) {
                  return { value: 'This', done: false};
                } else if (step === 2) {
                  return { value: 'is', done: false};
                } else if (step === 3) {
                  return { value: 'iterable.', done: false};
                }
                return { value: '', done: true };
              }
            }
          },
        }
        for (const val of iterableObj) {
          console.log(val);
        }

        // 출력
        // This
        // is 
        // iterable.
  • generator를 이용해서 만든 이터레이터

    Symbol.iterator() , next()가 이미 구현 되어있다

        function * iterableObj() {
          yield 'This';
          yield 'is';
          yield 'iterable.'
        }
        for (const val of iterableObj()) {
          console.log(val);
        }

        // 모든 generator 는 symbol.iterator와 .next()를 기본 내장 하고있다
        // 호출 될때마다 다음 yield 까지 구동
        // this가 바뀌고 next() 호출

        // 동일하게 출력

[어디에 generator 함수를 써야하는가]

  • Promise, async/ await
    • Promise 자체로도 비동기 프로그래밍을 다루는데 문제 없지만 generator를 이용하면 코드의 가독성과 관리 측면에서 좋다
        function * generator-promise() {
        let a=yield Promise1();
        console.log(a);

        let b=yield Promise1();
        console.log(b);

        let c=yield Promise1();
        console.log(c);
        }

        // generator-promise함수는 매번 실행하면서 yield를 만나 정지합니다.
        // 제네레이터 함수는 실행하면 Promise1 결괏값을 yield(넘겨주고)하고 함수를 종료합니다.
        // 다시 (제네레이터)함수가 실행하면 이 값을 변수 a에 저장됩니다.
        // 이런 과정을 통해 Promise 값이 변수 a, b, c에 저장되고 console.log를 통해 확인 가능합니다.
        // {value : 결과값, done: boolean값 }
  • Infinite Data Generator
    • generator는 기본적으로 값을 생성한다. 조건을 무한으로 하면 무한 데이터 생성도 가능하다
        function * randomize() {
          while (true) {
        let random = Math.floor(Math.random()*1000);
            yield random;
          }}

        var random= randomize();
        while(true) 
        console.log(random.next().value)

        function * naturalNumbers() {
          let num = 1;
          while (true) {
            yield num;
            num = num + 1
          }
        }
        const numbers = naturalNumbers();

        console.log(numbers.next().value)
        console.log(numbers.next().value)
        // 1
        // 2
  • 느긋한 계산이 가능하다

async/await

  • 동기코드와 유사한 스타일의 코딩스타일을 가능하게 하는게 장점, 최상위의 promise에 대해서 사용 하게된다. await로 선언시킨 함수 완료 후에 연결된 다른 함수가 실행 될 수 있게해준다.

[어떻게 구현되었는가]

    // ES7
    async function foo() {
      await bar();
    }

    // ES5 complied
    let foo = (() => {
      var _ref = _asyncToGenerator(function*() {
        yield bar();
      });

      return function foo() {
        return _ref.apply(this, arguments);
      };
    })();

    function _asyncToGenerator(fn) {
        // generator 함수를 받아온다
      return function() {
        var gen = fn.apply(this, arguments);
            // .apply로 generator 실행
            // iterator 객체를 gen 안에 넣어서 클로저로 저장해둔다

        return new Promise(function(resolve, reject) {
          function step(key, arg) {
            try {
              var info = gen[key](arg);
                            // next() 호출, iterator 객체 소환
              var value = info.value;
            } catch (error) {
              reject(error);
              return;
            }
            if (info.done) { // true 면 종료
              resolve(value);
            } else {
              return Promise.resolve(value).then(
                function(value) {
                  step("next", value);
                                // 재귀함수를 통해 반복실행
                },
                function(err) {
                  step("throw", err);
                }
              );
            }
          }
          return step("next");
        });
      };
    }
  • async 키워드를 generator 로 바꾸고 await 키워드는 yield 로 바꿨다

    → 비동기 로직이 완료 될때마다 적절하게 next() 를 호출하면 된다.

    bar() 함수의 종료시점은 bar() 밖에 모르고 next()bar() 내에서 실행하게 되면 의존성이 생긴다

    → Promise와 재귀함수를 이용하여 next()를 대신 호출하는 함수를 만들어준다 (_asyncToGenerator 함수)


참고글

Javascript와 Iterator

[Javascript] Iterator, Iterable 에 대해서

[Generator 함수를 이해해보자 (이론편)] 1. "Why" 제네레이터 함수를 써야 하는가?

Async-await는 어떻게 구현하는가

Top comments (0)