From fa9c3aa1fd0455d843f125619efedc05fb33bcdd Mon Sep 17 00:00:00 2001 From: Vladislav Botvin Date: Wed, 19 Jul 2023 22:59:01 +0300 Subject: [PATCH] feat: pipeLazy (#218) --- src/Lazy/index.ts | 2 + src/Lazy/pipeLazy.ts | 333 +++++++++++++++++++++++++++++++++++++ test/Lazy/pipeLazy.spec.ts | 93 +++++++++++ 3 files changed, 428 insertions(+) create mode 100644 src/Lazy/pipeLazy.ts create mode 100644 test/Lazy/pipeLazy.spec.ts diff --git a/src/Lazy/index.ts b/src/Lazy/index.ts index bf5919a7..222cb7f0 100644 --- a/src/Lazy/index.ts +++ b/src/Lazy/index.ts @@ -20,6 +20,7 @@ import intersectionBy from "./intersectionBy"; import keys from "./keys"; import map from "./map"; import peek from "./peek"; +import pipeLazy from "./pipeLazy"; import pluck from "./pluck"; import prepend from "./prepend"; import range from "./range"; @@ -64,6 +65,7 @@ export { keys, map, peek, + pipeLazy, pluck, prepend, range, diff --git a/src/Lazy/pipeLazy.ts b/src/Lazy/pipeLazy.ts new file mode 100644 index 00000000..3b746fa6 --- /dev/null +++ b/src/Lazy/pipeLazy.ts @@ -0,0 +1,333 @@ +import pipe1 from "../pipe1"; +import reduce from "../reduce"; +import type Awaited from "../types/Awaited"; +import type ReturnPipeType from "../types/ReturnPipeType"; + +/** + * Make function, that performs left to right function composition. + * All arguments must be unary. + * + * @example + * ```ts + * pipeLazy( + * map(a => a + 10), + * filter(a => a % 2 === 0), + * toArray, + * )([1, 2, 3, 4, 5]); // [12, 14] + * + * await pipeLazy( + * map(a => a + 10), + * filter(a => a % 2 === 0), + * toArray, + * )(Promise.resolve([1, 2, 3, 4, 5])); // [12, 14] + * + * // if you want to use asynchronous callback + * await pipeLazy( + * toAsync, + * map(async (a) => a + 10), + * filter((a) => a % 2 === 0), + * toArray, + * )(Promise.resolve([1, 2, 3, 4, 5])); // [12, 14] + * + * // with toAsync + * await pipeLazy( + * toAsync, + * map(a => a + 10), + * filter(a => a % 2 === 0), + * toArray, + * )([Promise.resolve(1), Promise.resolve(2), Promise.resolve(3), Promise.resolve(4), Promise.resolve(5)]); // [12, 14] + * ``` + * + * {@link https://codesandbox.io/s/fxts-toarray-fy84i | Try It} + * + * see {@link https://fxts.dev/docs/pipeLazy | pipeLazy}, {@link https://fxts.dev/docs/toAsync | toAsync}, + * {@link https://fxts.dev/docs/map | map}, {@link https://fxts.dev/docs/filter | filter} + */ +// eslint-disable-next-line +// @ts-ignore +// prettier-ignore +function pipeLazy( + f1: (a: Awaited) => R +): (a: T1) => ReturnPipeType<[T1,R]>; + +// prettier-ignore +function pipeLazy( + f1: (a: Awaited) => T2, + f2: (a: Awaited) => R +): (a: T1) => ReturnPipeType<[T1, T2, R]>; + +function pipeLazy( + f1: (a: Awaited) => T2, + f2: (a: Awaited) => T3, + f3: (a: Awaited) => R, +): (a: T1) => ReturnPipeType<[T1, T2, T3, R]>; + +function pipeLazy( + f1: (a: Awaited) => T2, + f2: (a: Awaited) => T3, + f3: (a: Awaited) => T4, + f4: (a: Awaited) => R, +): (a: T1) => ReturnPipeType<[T1, T2, T3, T4, R]>; + +function pipeLazy( + f1: (a: Awaited) => T2, + f2: (a: Awaited) => T3, + f3: (a: Awaited) => T4, + f4: (a: Awaited) => T5, + f5: (a: Awaited) => R, +): (a: T1) => ReturnPipeType<[T1, T2, T3, T4, T5, R]>; + +function pipeLazy( + f1: (a: Awaited) => T2, + f2: (a: Awaited) => T3, + f3: (a: Awaited) => T4, + f4: (a: Awaited) => T5, + f5: (a: Awaited) => T6, + f6: (a: Awaited) => R, +): (a: T1) => ReturnPipeType<[T1, T2, T3, T4, T5, T6, R]>; + +function pipeLazy( + f1: (a: Awaited) => T2, + f2: (a: Awaited) => T3, + f3: (a: Awaited) => T4, + f4: (a: Awaited) => T5, + f5: (a: Awaited) => T6, + f6: (a: Awaited) => T7, + f7: (a: Awaited) => R, +): (a: T1) => ReturnPipeType<[T1, T2, T3, T4, T5, T6, T7, R]>; + +function pipeLazy( + f1: (a: Awaited) => T2, + f2: (a: Awaited) => T3, + f3: (a: Awaited) => T4, + f4: (a: Awaited) => T5, + f5: (a: Awaited) => T6, + f6: (a: Awaited) => T7, + f7: (a: Awaited) => T8, + f8: (a: Awaited) => R, +): (a: T1) => ReturnPipeType<[T1, T2, T3, T4, T5, T6, T7, T8, R]>; + +function pipeLazy( + f1: (a: Awaited) => T2, + f2: (a: Awaited) => T3, + f3: (a: Awaited) => T4, + f4: (a: Awaited) => T5, + f5: (a: Awaited) => T6, + f6: (a: Awaited) => T7, + f7: (a: Awaited) => T8, + f8: (a: Awaited) => T9, + f9: (a: Awaited) => R, +): (a: T1) => ReturnPipeType<[T1, T2, T3, T4, T5, T6, T7, T8, T9, R]>; + +function pipeLazy( + f1: (a: Awaited) => T2, + f2: (a: Awaited) => T3, + f3: (a: Awaited) => T4, + f4: (a: Awaited) => T5, + f5: (a: Awaited) => T6, + f6: (a: Awaited) => T7, + f7: (a: Awaited) => T8, + f8: (a: Awaited) => T9, + f9: (a: Awaited) => T10, + f10: (a: Awaited) => R, +): (a: T1) => ReturnPipeType<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R]>; + +function pipeLazy( + f1: (a: Awaited) => T2, + f2: (a: Awaited) => T3, + f3: (a: Awaited) => T4, + f4: (a: Awaited) => T5, + f5: (a: Awaited) => T6, + f6: (a: Awaited) => T7, + f7: (a: Awaited) => T8, + f8: (a: Awaited) => T9, + f9: (a: Awaited) => T10, + f10: (a: Awaited) => T11, + f11: (a: Awaited) => R, +): (a: T1) => ReturnPipeType<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, R]>; + +function pipeLazy( + f1: (a: Awaited) => T2, + f2: (a: Awaited) => T3, + f3: (a: Awaited) => T4, + f4: (a: Awaited) => T5, + f5: (a: Awaited) => T6, + f6: (a: Awaited) => T7, + f7: (a: Awaited) => T8, + f8: (a: Awaited) => T9, + f9: (a: Awaited) => T10, + f10: (a: Awaited) => T11, + f11: (a: Awaited) => T12, + f12: (a: Awaited) => R, +): () => ReturnPipeType<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, R]>; + +function pipeLazy( + f1: (a: Awaited) => T2, + f2: (a: Awaited) => T3, + f3: (a: Awaited) => T4, + f4: (a: Awaited) => T5, + f5: (a: Awaited) => T6, + f6: (a: Awaited) => T7, + f7: (a: Awaited) => T8, + f8: (a: Awaited) => T9, + f9: (a: Awaited) => T10, + f10: (a: Awaited) => T11, + f11: (a: Awaited) => T12, + f12: (a: Awaited) => T13, + f13: (a: Awaited) => R, +): () => ReturnPipeType< + [T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, R] +>; + +// prettier-ignore +function pipeLazy( + f1: (a: Awaited) => T2, + f2: (a: Awaited) => T3, + f3: (a: Awaited) => T4, + f4: (a: Awaited) => T5, + f5: (a: Awaited) => T6, + f6: (a: Awaited) => T7, + f7: (a: Awaited) => T8, + f8: (a: Awaited) => T9, + f9: (a: Awaited) => T10, + f10: (a: Awaited) => T11, + f11: (a: Awaited) => T12, + f12: (a: Awaited) => T13, + f13: (a: Awaited) => T14, + f14: (a: Awaited) => R, +): (a: T1) => ReturnPipeType<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, R]>; + +// prettier-ignore +function pipeLazy( + f1: (a: Awaited) => T2, + f2: (a: Awaited) => T3, + f3: (a: Awaited) => T4, + f4: (a: Awaited) => T5, + f5: (a: Awaited) => T6, + f6: (a: Awaited) => T7, + f7: (a: Awaited) => T8, + f8: (a: Awaited) => T9, + f9: (a: Awaited) => T10, + f10: (a: Awaited) => T11, + f11: (a: Awaited) => T12, + f12: (a: Awaited) => T13, + f13: (a: Awaited) => T14, + f14: (a: Awaited) => T15, + f15: (a: Awaited) => R, +): (a: T1) => ReturnPipeType<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, R]>; + +// prettier-ignore +function pipeLazy( + f1: (a: Awaited) => T2, + f2: (a: Awaited) => T3, + f3: (a: Awaited) => T4, + f4: (a: Awaited) => T5, + f5: (a: Awaited) => T6, + f6: (a: Awaited) => T7, + f7: (a: Awaited) => T8, + f8: (a: Awaited) => T9, + f9: (a: Awaited) => T10, + f10: (a: Awaited) => T11, + f11: (a: Awaited) => T12, + f12: (a: Awaited) => T13, + f13: (a: Awaited) => T14, + f14: (a: Awaited) => T15, + f15: (a: Awaited) => T16, + f16: (a: Awaited) => R, +): (a: T1) => ReturnPipeType<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, R]>; + +// prettier-ignore +function pipeLazy( + f1: (a: Awaited) => T2, + f2: (a: Awaited) => T3, + f3: (a: Awaited) => T4, + f4: (a: Awaited) => T5, + f5: (a: Awaited) => T6, + f6: (a: Awaited) => T7, + f7: (a: Awaited) => T8, + f8: (a: Awaited) => T9, + f9: (a: Awaited) => T10, + f10: (a: Awaited) => T11, + f11: (a: Awaited) => T12, + f12: (a: Awaited) => T13, + f13: (a: Awaited) => T14, + f14: (a: Awaited) => T15, + f15: (a: Awaited) => T16, + f16: (a: Awaited) => T17, + f17: (a: Awaited) => R, +): (a: T1) => ReturnPipeType<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, R]>; + +// prettier-ignore +function pipeLazy( + f1: (a: Awaited) => T2, + f2: (a: Awaited) => T3, + f3: (a: Awaited) => T4, + f4: (a: Awaited) => T5, + f5: (a: Awaited) => T6, + f6: (a: Awaited) => T7, + f7: (a: Awaited) => T8, + f8: (a: Awaited) => T9, + f9: (a: Awaited) => T10, + f10: (a: Awaited) => T11, + f11: (a: Awaited) => T12, + f12: (a: Awaited) => T13, + f13: (a: Awaited) => T14, + f14: (a: Awaited) => T15, + f15: (a: Awaited) => T16, + f16: (a: Awaited) => T17, + f17: (a: Awaited) => T18, + f18: (a: Awaited) => R, +): (a: T1) => ReturnPipeType<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, R]>; + +// prettier-ignore +function pipeLazy( + f1: (a: Awaited) => T2, + f2: (a: Awaited) => T3, + f3: (a: Awaited) => T4, + f4: (a: Awaited) => T5, + f5: (a: Awaited) => T6, + f6: (a: Awaited) => T7, + f7: (a: Awaited) => T8, + f8: (a: Awaited) => T9, + f9: (a: Awaited) => T10, + f10: (a: Awaited) => T11, + f11: (a: Awaited) => T12, + f12: (a: Awaited) => T13, + f13: (a: Awaited) => T14, + f14: (a: Awaited) => T15, + f15: (a: Awaited) => T16, + f16: (a: Awaited) => T17, + f17: (a: Awaited) => T18, + f18: (a: Awaited) => T19, + f19: (a: Awaited) => R, +): (a: T1) => ReturnPipeType<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, R]>; + +// prettier-ignore +function pipeLazy( + f1: (a: Awaited) => T2, + f2: (a: Awaited) => T3, + f3: (a: Awaited) => T4, + f4: (a: Awaited) => T5, + f5: (a: Awaited) => T6, + f6: (a: Awaited) => T7, + f7: (a: Awaited) => T8, + f8: (a: Awaited) => T9, + f9: (a: Awaited) => T10, + f10: (a: Awaited) => T11, + f11: (a: Awaited) => T12, + f12: (a: Awaited) => T13, + f13: (a: Awaited) => T14, + f14: (a: Awaited) => T15, + f15: (a: Awaited) => T16, + f16: (a: Awaited) => T17, + f17: (a: Awaited) => T18, + f18: (a: Awaited) => T19, + f19: (a: Awaited) => T20, + f20: (a: Awaited) => R, +): (a: T1) => ReturnPipeType<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, R]>; + +function pipeLazy(...fns: any[]) { + return (a: any) => reduce(pipe1, a, fns); +} + +export default pipeLazy; diff --git a/test/Lazy/pipeLazy.spec.ts b/test/Lazy/pipeLazy.spec.ts new file mode 100644 index 00000000..d148a833 --- /dev/null +++ b/test/Lazy/pipeLazy.spec.ts @@ -0,0 +1,93 @@ +import { + delay, + each, + filter, + map, + pipeLazy, + range, + reduce, + toArray, + toAsync, +} from "../../src"; + +describe("pipeLazy", function () { + describe("sync", function () { + it("should return the value evaluated by the composed function", function () { + const res = pipeLazy( + (n: number) => String(n + 1), + (n) => Number(n) + 1, + (n) => String(n + 1), + )(0); + expect(res).toEqual("3"); + }); + + it("should work when the composed function deals with 'Iterable'", function () { + const res = pipeLazy( + (n: number[]) => map((a) => a + 5, n), + (n) => filter((a) => a % 2 === 0, n), + (n) => reduce((a, b) => a + b, 0, n), + )([1, 2, 3, 4, 5]); + expect(res).toEqual(24); + }); + }); + + describe("async", function () { + it("should work when the composed function deals with 'AsyncIterable'", async function () { + const res1 = await pipeLazy( + (n: AsyncIterableIterator) => map((a) => a + 5, n), + (n) => filter((a) => a % 2 === 0, n), + (n) => reduce((a, b) => a + b, 0, n), + )(toAsync([1, 2, 3, 4, 5])); + + expect(res1).toEqual(24); + + const res2 = await pipeLazy( + (n: AsyncIterableIterator) => map((a) => a + 5, n), + (n) => filter((a) => a % 2 === 0, n), + (n) => toArray(n), + (n) => reduce((a, b) => a + b, 0, n), + )(toAsync([1, 2, 3, 4, 5])); + + expect(res2).toEqual(24); + }); + + it("should return rejected 'Promise' if an error occurs in the callback", async function () { + await expect( + pipeLazy( + (a: AsyncIterableIterator) => + map(() => { + throw new Error("err"); + }, a), + (a) => reduce((acc: number, a) => acc + a, a), + )(toAsync(range(1, 10))), + ).rejects.toThrow("err"); + + // Promise reject + await expect( + pipeLazy( + (a: AsyncIterableIterator) => + map(() => Promise.reject(new Error("err")), a), + (a) => reduce((acc: number, a) => acc + a, a), + )(toAsync(range(1, 10))), + ).rejects.toThrow("err"); + }); + + it("should be able to use thenable object", async function () { + let res = 0; + + await pipeLazy( + toAsync, + each(() => { + return { + then: (resolve: any) => { + resolve(delay(0).then(() => res++)); + }, + catch: (reject: any) => reject(), + }; + }), + )(["a", "b", "c"]); + + expect(res).toBe(3); + }); + }); +});