Repository: marpple/FxTS
Pull request #288 (2024-10-14 06:21:13)

feat: add `to` to `fx`
Fixes #


indongyoo: 👍 

Patch:
diff --git a/src/Lazy/differenceBy.ts b/src/Lazy/differenceBy.ts
index da87fe61..58a26dac 100644
--- a/src/Lazy/differenceBy.ts
+++ b/src/Lazy/differenceBy.ts
@@ -1,3 +1,4 @@
+import { throwIfPromiseError } from "../_internal/error";
 import { isAsyncIterable, isIterable } from "../_internal/utils";
 import pipe from "../pipe";
 import pipe1 from "../pipe1";
@@ -13,7 +14,7 @@ function* sync<T>(
   iterable1: Iterable<T>,
   iterable2: Iterable<T>,
 ) {
-  const set = new Set(map(f, iterable1));
+  const set = new Set(map((a) => throwIfPromiseError(f(a)), iterable1));
 
   yield* pipe(
     iterable2,
diff --git a/src/Lazy/flatMap.ts b/src/Lazy/flatMap.ts
index c3000b26..1b1e3e34 100644
--- a/src/Lazy/flatMap.ts
+++ b/src/Lazy/flatMap.ts
@@ -1,3 +1,4 @@
+import { throwIfPromiseError } from "../_internal/error";
 import { isAsyncIterable, isIterable } from "../_internal/utils";
 import type Awaited from "../types/Awaited";
 import type { DeepFlat, DeepFlatSync } from "../types/DeepFlat";
@@ -87,12 +88,12 @@ function flatMap<
 ): ReturnFlatMapType<A, B> | ((iterable: A) => ReturnFlatMapType<A, B>) {
   if (iterable === undefined) {
     return (iterable: A) => {
-      return flat(map(f, iterable as any)) as any;
+      return flatMap(f, iterable) as any;
     };
   }
 
   if (isIterable<IterableInfer<A>>(iterable)) {
-    return flat(map(f, iterable as any)) as any;
+    return flat(map((a) => throwIfPromiseError(f(a)), iterable)) as any;
   }
 
   if (isAsyncIterable<Awaited<IterableInfer<A>>>(iterable)) {
diff --git a/src/Lazy/fx.ts b/src/Lazy/fx.ts
index 7ee8e164..6265c128 100644
--- a/src/Lazy/fx.ts
+++ b/src/Lazy/fx.ts
@@ -40,6 +40,22 @@ class FxAsyncIterable<A> {
     return this.asyncIterable[Symbol.asyncIterator]();
   }
 
+  /**
+   * It takes a user-defined function that transforms the current object and returns the result of that transformation.
+   *
+   * @example
+   * ```ts
+   * const arrSize = await fx([5, 2, 3, 1, 4, 5, 3])
+   *   .toAsync()
+   *   .filter((n) => n % 2 === 1)
+   *   .map((n) => n * 10)
+   *   .to((iterable) => size(uniq(iterable)));
+   * ```
+   */
+  to<R>(converter: (asyncIterable: this) => R): R {
+    return converter(this);
+  }
+
   /**
    * Returns AsyncIterable of values by running each applying `f`.
    *
@@ -269,6 +285,7 @@ class FxAsyncIterable<A> {
 
   /**
    * Iterates over AsyncIterable, applying each in turn to `f`.
+   * It works the same way as `forEach`.
    *
    * see {@link https://fxts.dev/docs/each | each}
    */
@@ -276,6 +293,16 @@ class FxAsyncIterable<A> {
     return each(f, this.asyncIterable);
   }
 
+  /**
+   * Iterates over AsyncIterable, applying each in turn to `f`.
+   * It works the same way as `each`.
+   *
+   * see {@link https://fxts.dev/docs/each | each}
+   */
+  async forEach(f: (a: A) => unknown): Promise<void> {
+    return each(f, this.asyncIterable);
+  }
+
   /**
    * Takes item from AsyncIterable and returns an array.
    *
@@ -301,6 +328,24 @@ export class FxIterable<A> {
     return this.iterable[Symbol.iterator]();
   }
 
+  /**
+   * It takes a user-defined function that transforms the current object and returns the result of that transformation.
+   *
+   *
+   * @example
+   * ```ts
+   * const size = fx([5, 2, 3, 1, 4, 5, 3])
+   *  .filter(n => n % 2 === 1)
+   *  .map(n => n * 10)
+   *  .to(iterable => new Set(iterable)) // convert set
+   *  .add(10)
+   *  .size;
+   * ```
+   */
+  to<R>(converter: (iterable: this) => R): R {
+    return converter(this);
+  }
+
   /**
    * Returns Iterable of values by running each applying `f`.
    *
@@ -516,6 +561,7 @@ export class FxIterable<A> {
 
   /**
    * Iterates over Iterable, applying each in turn to `f`.
+   * It works the same way as `forEach`.
    *
    * see {@link https://fxts.dev/docs/each | each}
    */
@@ -523,6 +569,16 @@ export class FxIterable<A> {
     return each(f, this.iterable);
   }
 
+  /**
+   * Iterates over Iterable, applying each in turn to `f`.
+   * It works the same way as `each`.
+   *
+   * see {@link https://fxts.dev/docs/each | each}
+   */
+  forEach(f: (a: A) => unknown): void {
+    return each(f, this.iterable);
+  }
+
   /**
    * Takes item from Iterable and returns an array.
    *
diff --git a/src/Lazy/intersectionBy.ts b/src/Lazy/intersectionBy.ts
index 91f76b24..1275dbb7 100644
--- a/src/Lazy/intersectionBy.ts
+++ b/src/Lazy/intersectionBy.ts
@@ -1,3 +1,4 @@
+import { throwIfPromiseError } from "../_internal/error";
 import { isAsyncIterable, isIterable } from "../_internal/utils";
 import pipe from "../pipe";
 import pipe1 from "../pipe1";
@@ -13,7 +14,7 @@ function* sync<T>(
   iterable1: Iterable<T>,
   iterable2: Iterable<T>,
 ) {
-  const set = new Set(map(f, iterable1));
+  const set = new Set(map((a) => throwIfPromiseError(f(a)), iterable1));
 
   yield* pipe(
     iterable2,
diff --git a/src/Lazy/map.ts b/src/Lazy/map.ts
index 9cf2cc14..30749182 100644
--- a/src/Lazy/map.ts
+++ b/src/Lazy/map.ts
@@ -20,14 +20,9 @@ function sync<A, B>(
         };
       }
 
-      const res = f(value);
-      if (isPromise(res)) {
-        throw new AsyncFunctionException();
-      }
-
       return {
         done: false,
-        value: res,
+        value: f(value),
       };
     },
     [Symbol.iterator]() {
diff --git a/src/Lazy/zipWith.ts b/src/Lazy/zipWith.ts
index 02658e10..5b770d72 100644
--- a/src/Lazy/zipWith.ts
+++ b/src/Lazy/zipWith.ts
@@ -1,3 +1,4 @@
+import { throwIfPromiseError } from "../_internal/error";
 import { isAsyncIterable, isIterable } from "../_internal/utils";
 import map from "./map";
 import zip from "./zip";
@@ -44,7 +45,10 @@ function zipWith<A, B, C>(
   iterable2: Iterable<B> | AsyncIterable<B>,
 ): IterableIterator<C> | AsyncIterableIterator<C> {
   if (isIterable(iterable1) && isIterable(iterable2)) {
-    return map(([a, b]) => f(a, b), zip(iterable1, iterable2));
+    return map(
+      ([a, b]) => throwIfPromiseError(f(a, b)),
+      zip(iterable1, iterable2),
+    );
   }
   if (isIterable(iterable1) && isAsyncIterable(iterable2)) {
     return map(([a, b]) => f(a, b), zip(iterable1, iterable2));
diff --git a/src/_internal/error.ts b/src/_internal/error.ts
index f93d63ac..10b3f820 100644
--- a/src/_internal/error.ts
+++ b/src/_internal/error.ts
@@ -1,3 +1,5 @@
+import { isPromise } from "./utils";
+
 export class AsyncFunctionException extends Error {
   static MESSAGE = `'Iterable' can not used with async function.
 If you want to deal with async function, see: [toAsync](https://fxts.dev/docs/toAsync)`;
@@ -6,3 +8,11 @@ If you want to deal with async function, see: [toAsync](https://fxts.dev/docs/to
     super(message);
   }
 }
+
+export const throwIfPromiseError = <T>(a: Promise<T> | T): T => {
+  if (isPromise(a)) {
+    throw new AsyncFunctionException();
+  }
+
+  return a;
+};
diff --git a/src/every.ts b/src/every.ts
index 32d645b6..6d5859c9 100644
--- a/src/every.ts
+++ b/src/every.ts
@@ -1,3 +1,4 @@
+import { throwIfPromiseError } from "./_internal/error";
 import { isAsyncIterable, isIterable } from "./_internal/utils";
 import map from "./Lazy/map";
 import takeUntil from "./Lazy/takeUntil";
@@ -56,7 +57,7 @@ function every<
 
   if (isIterable<IterableInfer<A>>(iterable)) {
     return pipe(
-      map(f, iterable),
+      map((a) => throwIfPromiseError(f(a)), iterable),
       takeUntil(not),
       (acc) =>
         reduce(
diff --git a/src/some.ts b/src/some.ts
index c216db9c..46dfc016 100644
--- a/src/some.ts
+++ b/src/some.ts
@@ -1,3 +1,4 @@
+import { throwIfPromiseError } from "./_internal/error";
 import { isAsyncIterable, isIterable } from "./_internal/utils";
 import identity from "./identity";
 import map from "./Lazy/map";
@@ -76,7 +77,7 @@ function some<
 
   if (isIterable<IterableInfer<A>>(iterable)) {
     return pipe(
-      map(f, iterable),
+      map((a) => throwIfPromiseError(f(a)), iterable),
       takeUntil(identity),
       (acc) =>
         reduce(


Test patch:
diff --git a/test/Lazy/fx.spec.ts b/test/Lazy/fx.spec.ts
index 45348c31..ef817d7e 100644
--- a/test/Lazy/fx.spec.ts
+++ b/test/Lazy/fx.spec.ts
@@ -1,4 +1,4 @@
-import { fx, toArray, toAsync } from "../../src";
+import { fx, size, toArray, toAsync, uniq } from "../../src";
 
 describe("fx", function () {
   describe("sync", function () {
@@ -16,6 +16,15 @@ describe("fx", function () {
         .toArray();
       expect(res).toEqual([11, 12, 13, 14, 15]);
     });
+
+    it("`to` method", function () {
+      const arrSize = fx([5, 2, 3, 1, 4, 5, 3])
+        .filter((n) => n % 2 === 1)
+        .map((n) => n * 10)
+        .to((iterable) => new Set(iterable))
+        .add(10).size;
+      expect(arrSize).toEqual(3);
+    });
   });
 
   describe("async", () => {
@@ -39,5 +48,15 @@ describe("fx", function () {
         .toArray();
       expect(res2).toEqual([11, 12, 13, 14, 15]);
     });
+
+    it("`to` method", async function () {
+      const arrSize = await fx([5, 2, 3, 1, 4, 5, 3])
+        .toAsync()
+        .filter((n) => n % 2 === 1)
+        .map((n) => n * 10)
+        .to((iterable) => size(uniq(iterable)));
+
+      expect(arrSize).toEqual(3);
+    });
   });
 });
diff --git a/test/Lazy/map.spec.ts b/test/Lazy/map.spec.ts
index f2ae9f19..eba7d636 100644
--- a/test/Lazy/map.spec.ts
+++ b/test/Lazy/map.spec.ts
@@ -1,4 +1,3 @@
-import { AsyncFunctionException } from "../../src/_internal/error";
 import { fx, map, pipe, range, toArray, toAsync } from "../../src/index";
 import { Concurrent } from "../../src/Lazy/concurrent";
 import { generatorMock } from "../utils";
@@ -16,11 +15,6 @@ describe("map", function () {
       expect(acc).toEqual([11, 12, 13, 14, 15]);
     });
 
-    it("should throw an error when the callback is asynchronous", function () {
-      const res = () => [...map(add10Async, [1, 2, 3, 4, 5])];
-      expect(res).toThrowError(new AsyncFunctionException());
-    });
-
     it("should be able to be used as a curried function in the pipeline", function () {
       const res = pipe(
         [1, 2, 3, 4],
@@ -92,6 +86,18 @@ describe("map", function () {
 
       expect(res).toEqual(["1", "2", "3", "4"]);
     });
+    it("should be able to be used as a promise function", async function () {
+      const res1 = map(async (a) => a, [1, 2, 3, 4]);
+
+      expect(await Promise.all(res1)).toEqual([1, 2, 3, 4]);
+
+      const res2 = pipe(
+        [1, 2, 3, 4],
+        map(async (a) => a),
+      );
+
+      expect(await Promise.all(res2)).toEqual([1, 2, 3, 4]);
+    });
 
     it("should be able to be used as a chaining method in the `fx`", async function () {
       const res = await fx(toAsync([1, 2, 3, 4]))
diff --git a/type-check/Lazy/map.test.ts b/type-check/Lazy/map.test.ts
index 093b13ec..70288969 100644
--- a/type-check/Lazy/map.test.ts
+++ b/type-check/Lazy/map.test.ts
@@ -30,6 +30,12 @@ const res10 = pipe(
   map(async (a) => a),
 );
 
+const res11 = map(async (a) => a, [1, 2, 3, 4]);
+const res12 = pipe(
+  [1, 2, 3, 4],
+  map(async (a) => a),
+);
+
 checks([
   check<typeof res1, IterableIterator<number>, Test.Pass>(),
   check<typeof res2, IterableIterator<string>, Test.Pass>(),
@@ -42,4 +48,7 @@ checks([
   check<typeof res8, IterableIterator<Promise<number>>, Test.Pass>(),
   check<typeof res9, AsyncIterableIterator<number>, Test.Pass>(),
   check<typeof res10, AsyncIterableIterator<number>, Test.Pass>(),
+
+  check<typeof res11, IterableIterator<Promise<number>>, Test.Pass>(),
+  check<typeof res12, IterableIterator<Promise<number>>, Test.Pass>(),
 ]);