Repository: marpple/FxTS
Pull request #58 (2021-12-08 09:06:30)

fix: handle tap async
why?

If the auxiliary function of tap is asynchronous, it is more natural to wait and return the original value.



Patch:
diff --git a/src/Lazy/peek.ts b/src/Lazy/peek.ts
index ca15304f..13620a77 100644
--- a/src/Lazy/peek.ts
+++ b/src/Lazy/peek.ts
@@ -1,6 +1,7 @@
 import tap from "../tap";
 import map from "./map";
 import { isAsyncIterable, isIterable } from "../_internal/utils";
+import Awaited from "../types/Awaited";
 import ReturnIterableIteratorType from "../types/ReturnIterableIteratorType";
 import IterableInfer from "../types/IterableInfer";
 
@@ -63,15 +64,15 @@ function peek<T>(
 ): AsyncIterableIterator<T>;
 
 function peek<T extends Iterable<unknown> | AsyncIterable<unknown>>(
-  f: (a: IterableInfer<T>) => unknown,
+  f: (a: Awaited<IterableInfer<T>>) => unknown,
 ): (iterable: T) => ReturnIterableIteratorType<T>;
 
 function peek<T extends Iterable<unknown> | AsyncIterable<unknown>>(
-  f: (a: IterableInfer<T>) => unknown,
+  f: (a: Awaited<IterableInfer<T>>) => unknown,
   iterable?: T,
 ):
-  | IterableIterator<IterableInfer<T>>
-  | AsyncIterableIterator<IterableInfer<T>>
+  | IterableIterator<Awaited<IterableInfer<T>>>
+  | AsyncIterableIterator<Awaited<IterableInfer<T>>>
   | ((iterable: T) => ReturnIterableIteratorType<T>) {
   if (iterable === undefined) {
     return (iterable: T) => {
@@ -82,15 +83,15 @@ function peek<T extends Iterable<unknown> | AsyncIterable<unknown>>(
   if (isIterable(iterable)) {
     return map(
       tap(f),
-      iterable as Iterable<IterableInfer<T>>,
-    ) as ReturnIterableIteratorType<T, IterableInfer<T>>;
+      iterable as Iterable<Awaited<IterableInfer<T>>>,
+    ) as ReturnIterableIteratorType<T, Awaited<IterableInfer<T>>>;
   }
 
   if (isAsyncIterable(iterable)) {
     return map(
       tap(f),
-      iterable as AsyncIterable<IterableInfer<T>>,
-    ) as ReturnIterableIteratorType<T, IterableInfer<T>>;
+      iterable as AsyncIterable<Awaited<IterableInfer<T>>>,
+    ) as ReturnIterableIteratorType<T, Awaited<IterableInfer<T>>>;
   }
 
   throw new TypeError("'iterable' must be type of Iterable or AsyncIterable");
diff --git a/src/index.ts b/src/index.ts
index 39a81097..afccfc07 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -27,7 +27,6 @@ import pipe from "./pipe";
 import pipe1 from "./pipe1";
 import reduce from "./reduce";
 import some from "./some";
-import sort from "./sort";
 import tap from "./tap";
 import toArray from "./toArray";
 
@@ -63,7 +62,6 @@ export {
   pipe1,
   reduce,
   some,
-  sort,
   tap,
   toArray,
 };
diff --git a/src/sort.ts b/src/sort.ts
deleted file mode 100644
index 0fbc03ec..00000000
--- a/src/sort.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-import { isArray, pipe1, toArray } from ".";
-import IterableInfer from "./types/IterableInfer";
-import ReturnValueType from "./types/ReturnValueType";
-import { isAsyncIterable, isIterable } from "./_internal/utils";
-
-/**
- * Returns an array which sorted according to the `f`
- *
- * @example
- * ```ts
- * sort((a, b) => a > b, [3, 4, 1, 2, 5, 2]); // [1, 2, 2, 3, 4, 5]
- * sort((a, b) => a > b, 'bcdaef); // ["a", "b", "c", "d", "e", "f"]
- * ```
- */
-// prettier-ignore
-function sort(
-    f: (a: any, b: any) => unknown, 
-    iterable: readonly []
-  ): any[];
-
-// prettier-ignore
-function sort<T>(
-  f: (a: T, b: T) => unknown, 
-  iterable: Iterable<T>
-): T[];
-
-function sort<T>(
-  f: (a: T, b: T) => unknown,
-  iterable: AsyncIterable<T>,
-): Promise<T[]>;
-
-function sort<T extends Iterable<unknown> | AsyncIterable<unknown>>(
-  f: (a: IterableInfer<T>, b: IterableInfer<T>) => unknown,
-): (iterable: T) => ReturnValueType<T, IterableInfer<T>[]>;
-
-function sort<T extends Iterable<unknown> | AsyncIterable<unknown>>(
-  f: (a: T, b: T) => unknown,
-  iterable?: T,
-):
-  | IterableInfer<T>[]
-  | Promise<IterableInfer<T>[]>
-  | ((iterable: T) => ReturnValueType<T, IterableInfer<T>[]>) {
-  if (iterable === undefined) {
-    return (iterable: T) => {
-      return sort(f, iterable as any) as ReturnValueType<T, IterableInfer<T>[]>;
-    };
-  }
-
-  if (isArray(iterable)) {
-    return iterable.sort(f as any);
-  }
-
-  if (isIterable(iterable)) {
-    return pipe1(toArray(iterable as Iterable<IterableInfer<T>>), (arr) =>
-      arr.sort(f as any),
-    );
-  }
-
-  if (isAsyncIterable(iterable)) {
-    return pipe1(toArray(iterable as AsyncIterable<IterableInfer<T>>), (arr) =>
-      arr.sort(f as any),
-    );
-  }
-
-  throw new TypeError("'iterable' must be type of Iterable or AsyncIterable");
-}
-
-export default sort;
diff --git a/src/tap.ts b/src/tap.ts
index 414534ac..311d85d7 100644
--- a/src/tap.ts
+++ b/src/tap.ts
@@ -1,3 +1,4 @@
+import Awaited from "./types/Awaited";
 /**
  * This method invokes interceptor and returns a value.
  * The interceptor is invoked with one argument.
@@ -7,17 +8,40 @@
  * tap(console.log, [1,2,3,4,5])
  * // log [1, 2, 3, 4, 5]
  * // return [1, 2, 3, 4, 5]
+ *
+ * tap(async (a) => console.log(a), [1,2,3,4,5]);
+ * // log [1, 2, 3, 4, 5]
+ * // return Promise<[1, 2, 3, 4, 5]>
  * ```
  */
-function tap<T>(f: (arg: T) => unknown, v: T): T;
+function tap<T, U>(
+  f: (arg: Awaited<T>) => U,
+  v: T,
+): U extends Promise<any> ? Promise<Awaited<T>> : T;
 
-function tap<T>(f: (arg: T) => unknown): (v: T) => T;
+function tap<T, U>(
+  f: (arg: Awaited<T>) => U,
+): (v: T) => U extends Promise<any> ? Promise<Awaited<T>> : T;
 
-function tap<T>(f: (arg: T) => unknown, v?: T): T | ((v: T) => T) {
+function tap<T, U>(
+  f: (arg: Awaited<T>) => U,
+  v?: T,
+):
+  | T
+  | Promise<T>
+  | ((v: T) => U extends Promise<any> ? Promise<Awaited<T>> : T) {
   if (v === undefined) {
-    return (v: T) => (v instanceof Promise ? v.then(f) : f(v), v);
+    return (v: T) =>
+      (v instanceof Promise ? v.then(f) : f(v as Awaited<T>),
+      v) as U extends Promise<any> ? Promise<Awaited<T>> : T;
   }
-  return v instanceof Promise ? v.then(f) : f(v), v;
+
+  const res = v instanceof Promise ? v.then(f) : f(v as Awaited<T>);
+  if (res instanceof Promise) {
+    return res.then(() => v);
+  }
+
+  return v;
 }
 
 export default tap;


Test patch:
diff --git a/test/sort.spec.ts b/test/sort.spec.ts
deleted file mode 100644
index 8bd6f620..00000000
--- a/test/sort.spec.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-import { filter, pipe, sort, toAsync } from "../src/index";
-
-describe("sort", function () {
-  const sortFn = (a: number | string, b: number | string) => {
-    if (a === b) {
-      return 0;
-    }
-    if (a > b) {
-      return 1;
-    }
-    return -1;
-  };
-
-  describe("sync", function () {
-    it("should return 'false' if given iterable is an empty array", function () {
-      expect(sort(sortFn, [])).toEqual([]);
-      expect(sort(sortFn, [3, 4, 1, 2, 5, 2])).toEqual([1, 2, 2, 3, 4, 5]);
-      expect(sort(sortFn, "bcdaef")).toEqual(["a", "b", "c", "d", "e", "f"]);
-    });
-
-    it("should be able to be used as a curried function in the pipeline", function () {
-      const res = pipe(
-        [3, 4, 1, 2, 5, 2],
-        filter((a) => a % 2 !== 0),
-        sort(sortFn),
-      );
-      expect(res).toEqual([1, 3, 5]);
-    });
-  });
-
-  describe("async", function () {
-    it("should return 'false' if given iterable is an empty array", async function () {
-      const sortFn = (a: number | string, b: number | string) => {
-        if (a === b) {
-          return 0;
-        }
-        if (a > b) {
-          return 1;
-        }
-        return -1;
-      };
-
-      expect(await sort(sortFn, toAsync([]))).toEqual([]);
-      expect(await sort(sortFn, toAsync([3, 4, 1, 2, 5, 2]))).toEqual([
-        1, 2, 2, 3, 4, 5,
-      ]);
-      expect(await sort(sortFn, toAsync("bcdaef"))).toEqual([
-        "a",
-        "b",
-        "c",
-        "d",
-        "e",
-        "f",
-      ]);
-    });
-
-    it("should be able to be used as a curried function in the pipeline", async function () {
-      const res = await pipe(
-        [3, 4, 1, 2, 5, 2],
-        toAsync,
-        filter((a) => a % 2 !== 0),
-        sort(sortFn),
-      );
-      expect(res).toEqual([1, 3, 5]);
-    });
-  });
-});
diff --git a/test/tap.spec.ts b/test/tap.spec.ts
index 37c020ce..c1bf837e 100644
--- a/test/tap.spec.ts
+++ b/test/tap.spec.ts
@@ -1,25 +1,38 @@
-import { tap } from "../src/index";
+import { delay, tap } from "../src/index";
+import { callFuncAfterTime } from "./utils";
 
 describe("tap", function () {
-  it("should return the received argument as it is", function () {
-    const res = tap((a) => a * 100, 10);
-    expect(res).toEqual(10);
-  });
+  describe("sync", function () {
+    it("should return the received argument as it is", function () {
+      const res = tap((a) => a * 100, 10);
+      expect(res).toEqual(10);
+    });
 
-  it("should invoke the given callback once", function () {
-    const fn = jest.fn((a: number) => a * 100);
-    tap(fn, 10);
-    expect(fn).toBeCalled();
-  });
+    it("should invoke the given callback once", function () {
+      const fn = jest.fn((a: number) => a * 100);
+      tap(fn, 10);
+      expect(fn).toBeCalled();
+    });
 
-  it("should be curried when only one function is given as argument", function () {
-    let res1 = 10;
-    const res2 = tap((a: number) => {
-      res1 += a;
-      return res1;
-    })(50);
+    it("should be curried when only one function is given as argument", function () {
+      let res1 = 10;
+      const res2 = tap((a: number) => {
+        res1 += a;
+        return res1;
+      })(50);
+
+      expect(res1).toEqual(60);
+      expect(res2).toEqual(50);
+    });
+  });
 
-    expect(res1).toEqual(60);
-    expect(res2).toEqual(50);
+  describe("async", function () {
+    it("should return the received argument as it is", async function () {
+      const fn = jest.fn();
+      callFuncAfterTime(fn, 1000);
+      const res = await tap((a) => delay(1000, a), 10);
+      expect(fn).toBeCalled();
+      expect(res).toEqual(10);
+    }, 1050);
   });
 });
diff --git a/type-check/sort.test.ts b/type-check/sort.test.ts
deleted file mode 100644
index e1835f78..00000000
--- a/type-check/sort.test.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import * as Test from "../src/types/Test";
-import { pipe, sort, toAsync } from "../src";
-
-const { checks, check } = Test;
-
-const res0 = sort((a, b) => a > b, []);
-const res1 = sort((a, b) => a > b, [1, 2, 3]);
-const res2 = sort((a, b) => a > b, "abc");
-
-const res3 = sort((a, b) => a > b, toAsync([1, 2, 3]));
-const res4 = sort((a, b) => a > b, toAsync("abc"));
-
-const res5 = pipe(
-  [1, 2, 3],
-  sort((a, b) => a > b),
-);
-
-const res6 = pipe(
-  [1, 2, 3],
-  toAsync,
-  sort((a, b) => a > b),
-);
-
-checks([
-  check<typeof res0, any[], Test.Pass>(),
-  check<typeof res1, number[], Test.Pass>(),
-  check<typeof res2, string[], Test.Pass>(),
-  check<typeof res3, Promise<number[]>, Test.Pass>(),
-  check<typeof res4, Promise<string[]>, Test.Pass>(),
-  check<typeof res5, number[], Test.Pass>(),
-  check<typeof res6, Promise<number[]>, Test.Pass>(),
-]);
diff --git a/type-check/tap.test.ts b/type-check/tap.test.ts
index 5da8d12e..7292506d 100644
--- a/type-check/tap.test.ts
+++ b/type-check/tap.test.ts
@@ -5,13 +5,16 @@ const { checks, check } = Test;
 
 const res1 = tap((a) => a, 123);
 const res2 = tap((a) => Promise.resolve(a), "abc");
-
-const res1Promise = tap((a) => a, Promise.resolve(123));
-const res2Promise = tap((a) => Promise.resolve(a), Promise.resolve("abc"));
+const res3 = tap((a) => a, Promise.resolve(123));
+const res4 = tap((a) => Promise.resolve(a), Promise.resolve("abc"));
+const res5 = tap((a) => (a > 5 ? a : Promise.resolve(a)), Math.random() * 10);
+const res6 = tap((a) => (a > 5 ? a : Promise.resolve(a)), Promise.resolve(Math.random() * 10)); // prettier-ignore
 
 checks([
   check<typeof res1, number, Test.Pass>(),
-  check<typeof res2, string, Test.Pass>(),
-  check<typeof res1Promise, Promise<number>, Test.Pass>(),
-  check<typeof res2Promise, Promise<string>, Test.Pass>(),
+  check<typeof res2, Promise<string>, Test.Pass>(),
+  check<typeof res3, Promise<number>, Test.Pass>(),
+  check<typeof res4, Promise<string>, Test.Pass>(),
+  check<typeof res5, number | Promise<number>, Test.Pass>(),
+  check<typeof res6, Promise<number>, Test.Pass>(),
 ]);