[ Fxts ] Fxts 에서 AsyncIterable 다루기

UI SEOK YU·2023년 5월 29일
0

TypeScript

목록 보기
2/4
post-thumbnail

Iterable은 비동기함수와 함께 사용할 수 없다.

fxts를 활용하여 코드를 작성하다 처음 보는 에러와 조우했다.

"Iterable은 비동기함수와 함께 사용할 수 없음."

친절하게 공식문서로 안내한다. 에러를 해결하기 위해 참고했다.




라이브러리 공식문서

toAsync 의 필요성 : https://fxts.dev/docs/to-async

// 코드 1
const numbers = function* () {
  yield 1;
  yield 2;
  yield 3;
};

const asyncNumbers = async function* () {
  yield 1;
  yield 2;
  yield 3;
};

find((num) => num === 2, numbers()); // 2
find((num) => num === 2, asyncNumbers()); // Promise<2>

Fxts에서는 많은 함수들이 Iterable 또는 AsyncIterable 로 다루어 진다.
AsyncIterable 콜백 함수가 동기/비동기 실행 여부에 관계없이 은 정상적으로 작동할 수 있지만,
Iterable 은 비동기 콜백 함수를 사용하여 반복하거나 프로미스를 내포한Iterable<Promise<T>> 경우는 제대로 동작하지 않는다.

IterableAsyncIterable과 다르게 동기적인 것만 다룰 수 있다는 점이 중요하다.

// 코드 2
const promiseNumbers = function* () {
  yield Promise.resolve(1);
  yield Promise.resolve(2);
  yield Promise.resolve(3);
};

find((num) => Promise.resolve(num === 2), numbers()); // not work
find((num) => num === 2, promiseNumbers()); // not work

예시를 보면, 제너레이터를 AsyncIterable 로 선언하지 않았다.
( promiseNumbers = async function * () {} 형태여야 함. )
따라서 내부에 프로미스를 다룰 수 없으므로, 해당 이터레이터를 가지고 활용하려고 해도 불가능하다.

결국 내부에 프로미스와 같은 비동기 항목이 포함될 경우, 이를 Iterable 이 아닌 AsyncIterable 로 변경해 주어야 한다.

// 코드 3
await pipe(
  numbers(), // Iterable<number>
  toAsync, // AsyncIterable<number>
  find((num) => num === 2),
  console.log
);

await pipe(
  promiseNumbers(), // Iterable<Promise<number>>
  toAsync, // AsyncIterable<number>
  find((num) => num === 2),
  console.log
);
2
2

위 코드는 어떻게 동작했을까?

toAsync 메서드가 어떻게 구현 되었는지 찾아보자.




toAsync의 동작원리

toAsync : https://github.com/marpple/FxTS/blob/main/src/Lazy/toAsync.ts

// 코드 4
function toAsync<T>(iter: Iterable<T | Promise<T>>): AsyncIterableIterator<T> {
  const iterator = iter[Symbol.iterator]();
  return {
    async next() {
      const { value, done } = iterator.next();
      if (isPromise(value)) {
        return value.then((value) => ({ done, value }));
      } else {
        return { done, value };
      }
    },
    [Symbol.asyncIterator]() {
      return this;
    },
  };
}

toAsync는 객체에 next()[Symbol.asyncIterator]를 구현하여, 이터러블 객체를 반환하고 있다.
next()를 보면, 프로미스 객체일 때, then()을 통해 프로미스가 완료되면 해당 값을 추출하여 반환한다.

따라서 코드 2를 다시 살펴보면,
Iterable<number> 타입이거나, Iterable<Promise<number>> 타입인 경우 제대로 동작하지 않았을 것이다.
toAsync 함수를 사용하여 Iterable 에서 AsyncIterable 로 변경하였다.

find()정의에 따라 curry로 주어지는 인자는 AsyncIterable 일 것이고,
콜백에 들어가는 num은 AsyncIterable 에서 next()로 추출된 값 이다.
toAsync 으로 만들어진 이터러블은 프로미스를 받으므로(Promise.resolve(1) 등..)
then()을 통해 resolve 된 값을 추출한다. 따라서 find의 num은 1,2,3이 된다.

console.log()는 pipe를 통해 앞의 제너레이터로부터 값을 전달받는다.
find 조건문을 통과하는 건 2밖에 없으므로, 2가 찍힌다.

🙋
공식 도큐먼트에는 find콜백에 (num) => Promise.resolve(num === 2) 가 들어가 있는데,
애초에 toAsync에서 값을 넘겨줄 때 이미 resolve 값을 추출하여 넘긴다.
또한 pipe 에서도 값을 다음 함수로 넘길 때, Await형태로 추출된 값을 사용하는 것 같은데
왜 굳이 저런 형태로 조건문을 주는 지 모르겠다.
하지만 단순 조건문이어도 정상동작하고, 목적은 toAsync가 필요한 이유를 알아보는 것이므로 코드르 간단하게 수정했다.

0개의 댓글