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>>
경우는 제대로 동작하지 않는다.
Iterable
은 AsyncIterable
과 다르게 동기적인 것만 다룰 수 있다는 점이 중요하다.
// 코드 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
: 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가 필요한 이유를 알아보는 것이므로 코드르 간단하게 수정했다.