DEV Community

Steve Sung
Steve Sung

Posted on

2 2

[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는 어떻게 구현하는가

Image of Datadog

How to Diagram Your Cloud Architecture

Cloud architecture diagrams provide critical visibility into the resources in your environment and how they’re connected. In our latest eBook, AWS Solution Architects Jason Mimick and James Wenzel walk through best practices on how to build effective and professional diagrams.

Download the Free eBook

Top comments (0)

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more