asyncIterator 살펴보기

blackbell·2020년 7월 2일
1

javascript

목록 보기
3/6

개념정리

Flow란?
우리가 만든 파일이 메모리에 명령과 데이터로 나눠져서 적재된 후,
명령이 순차적으로 실행되는 과정.

blocking, non-blocking

blocking : Sync flow가 실행되는 동안 다른 일을 할 수 없는 현상
non-blocking : Sync Flow가 납득할 만한 시간 내에 종료되는 것

sync, async

Sync : 서브루틴이 즉시 값을 반환하는 것
Async : 서브루틴이 다른 수단으로 값을 반환하는 것

  • 우리가 원하는 값을 즉시 리턴하지 않는 모든 함수가 async 함수
  • 다른수단이란? Promise, callback function, iterations etc

code sample(codesandbox)

다음 코드정도의 기본지식이 등장합니다. url들에 대한 비동기처리를 병렬적으로 진행합니다.

const render = function(...urls){
   Promise.all(urls.map(url=>fetch(url).then(res => res.json())))
      .then(arr=>{
          arr.forEach(console.log);
      });
};
render("1.json", "2.json", "3.json");

우리는 이와 다르게 순차적으로 비동기처리를 해야되는 경우도 있습니다.

const render = function(...urls) {
  const loop = () => {
    if (urls.length) {
      fetch(urls.shift())
        .then(res => res.json())
        .then(json => {
        console.log(json);
        loop();
      });
    }
  };
  loop();
};
render("1.json", "2.json", "3.json");

다음처럼 loop 함수를 통해 순차적으로 비동기처리를 할 수 있습니다. 위의 있는 코드 중 console.log를 비동기처리를 통해 데이터를 가져온 후 진행하는 코드(렌더링, 데이터처리..)라고 간단하게 생각하겠습니다. 그렇게 되면 위의 코드를 데이터 로딩하는 부분과 처리하는 부분이 분리되지 않을 코드이므로 변경의 이유가 하나가 아닐 뿐더러, 데이터 처리하는 부분의 로직이 길어지게 되면 수정하기가 까다로울 것입니다.

generator and executor

아래 코드를 보면 generator를 이용하여 데이터 로딩하는 로직과 데이터처리하는 분리할 수 있습니다. 즉, 변화율이 다른 코드를 분리하였습니다. 게다가 위의 비동기적인 코드를 동기적으로 보이게 합니다. 가독성도 높아질 뿐만 아니라 개발자가 쉽게 코드를 짜게 해줍니다.
generator를 이용하면 제어구조를 그대로 둔 상태에서 변경되는 코드만 외부에 둘 수 있습니다. 보통 이렇게 외부에서 제네레이터의 제어구조를 이용하는 쪽을 실행기(executor)라고 합니다.

const dataLoader = function*(f, ...urls) {
  for (const url of urls) {
    const json = yield fetch(url).then(res => res.json()); // (*)
    f(json);
  }
};
const render = function(...urls) {
  const iter = dataLoader(console.log, ...urls);
  const nextLoop = ({ value, done }) => {
    if (!done) value.then(v => nextLoop(iter.next(v))); // (**)
  };
  nextLoop(iter.next());
};
render("1.json", "2.json", "3.json");

위의 코드는 첫번째로 iterator의 next를 호출하여 nextLoop의 인자값으로 넘겨줍니다. 이때 value에는 ()에서의 promise객체가 들어갑니다. 그 이후에 (**)가 실행이 되면 then으로 받아온 데이터를 넘겨주고 계속해서 nextLoop를 실행합니다. 그렇게 되면 ()에서 json값으로 then으로 받아온 데이터가 넘어가면서 dataLoader의 인자로 넘어간 console.log를 찍게됩니다. 이런 식으로 반복적으로 실행됩니다.

사실 console.log가 인자로 넘어갔기 때문에 아래와 같은 코드로 변경하면 확실하게 generator쪽으로 데이터 로딩에 대한 제어를 위임할 수 있습니다.
즉, 제네레이터의 비동기 사용이란 결국 실행기와 제네레이터를 이용하여 적절한 시점에 이터레이션을 진행시키는 기법이라고 할 수 있습니다.

const dataLoader = function*(...urls) {
  for (const url of urls) {
    const json = yield fetch(url).then(res => res.json());
    yield json;
  }
};
const render = function(...urls) {
  const iter = dataLoader(...urls);
  const next = ({ value, done }) => {
    if (!done) {
      if (value instanceof Promise) value.then(v => next(iter.next(v)));
      else {
        console.log(value);
        next(iter.next());
      }
    }
  };
  next(iter.next());
};
render("1.json", "2.json", "3.json");

asyncIterator, async generator

iterableasync iterable
iterator를 반환하는 메서드Symbol.iteratorSymbol.asyncIterator
next()가 반환하는 값{value:…, done: true/false}{value:…, done: true/false}를 감싸는 Promise

generatorasync generator
선언function*async function*
next()가 반환하는 값{value:…, done: true/false}{value:…, done: true/false}를 감싸는 Promise

위의 iterable, async iterable의 차이는 간단하게 보면 IteratorResultObject를 Promise로 감싸서 주는 것입니다.

위에서 봤던 코드를 다시 살펴보겠습니다.

// 이전코드
const dataLoader = function*(f, ...urls) {
  for (const url of urls) {
    const json = yield fetch(url).then(res => res.json()); // (*)
    f(json);
  }
};
// 2
const dataLoader = async function(f, ...urls) {
  for (const url of urls) {
    const json = await fetch(url).then(res => res.json()); // (*)
    f(json);
  }
};

이전코드를 async/await를 이용하여 바꿔보면 어디서 본듯한 코드가 됩니다.
즉, 비동기처리를 async/await를 이용하여 순차적으로 진행시키면 다음과 같은 코드가 됩니다.

const render = async function(...urls) {
  for (const url of urls) {
    console.log(await (await fetch(url)).json());
  }
};
render("1.json", "2.json", "3.json");

위에서와 같이 데이터 로딩에 대한 부분을 generator를 이용하여 역할을 분리하고 싶습니다. 이 때 방금전에 보았던 async generator가 나옵니다.

const dataLoader = async function*(...urls) {
  for (const url of urls) {
    yield await (await fetch(url)).json();
  }
};
const render = async function(...urls) {
  for await (const json of dataLoader(...urls)) {
    console.log(json);
  }
};
render("1.json", "2.json", "3.json");
};

async generator 함수는 asyncIterable를 반환하고 for await ~ of는 asyncIterable을 iterating할 수 있게 해줍니다.
for await ~ of MDN

느낀점

추후에 asyncIterator를 이용한 병렬적인 처리도 공부해야 합니다. Redux-saga를 보면 generator를 넘겨주어서 사용하게 되는데 이는 generator-executor 패턴에서 보게 되면 Redux-saga 내에 executor가 있음을 짐작할 수 있다. asyncIterator로 Redux-saga처럼 비동기처리를 할 수 있는지 비교할 수 기회가 있으면 하도록 하겠습니다.
공부를 하다 보니 어려움에 부딪히는게 너무 많아 이해를 다 못한 거 같습니다😭 Continuation Passing Style(자료도 잘 못 찾겠다😇), generator를 통한 flow control 등...
지속적으로 공부를 해야될 거 같습니다.

출처

profile
알고 싶은게 많은 프론트엔드 개발자입니다.

0개의 댓글