Repository: marpple/FxTS
Pull request #309 (2025-03-09 04:40:43)

feat: add `when`
Fixes https://github.com/marpple/FxTS/issues/305

The initial when function was designed to always terminate the pipeline based on the given condition. However, after implementing it, I realized that it was functionally identical to throwIf.

To enhance its differentiation, I added the following features:

1. The first argument accepts a Boolean value.
2. If the function in the second argument returns a value, that value is passed to the next pipe. If it returns nothing, an error is thrown to terminate the pipeline.

If the existing throwIf function is deemed sufficient, the when function may not be necessary.

ppeeou: @freevuehub  thank you for your contribution🎉

As you wrote in the issue, `when` seems to be useful just because it doesn't return an error if the predicate condition is met, unlike `throwIf`.
I think it would be good if you added it as you wrote in the issue!


I also request that you write a type test.
https://github.com/marpple/FxTS/tree/main/type-check
freevuehub: @ppeeou 
I have written it.

Patch:
diff --git a/src/index.ts b/src/index.ts
index 829a7397..7921b402 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -64,6 +64,7 @@ import throwIf from "./throwIf";
 import toArray from "./toArray";
 import unicodeToArray from "./unicodeToArray";
 import unless from "./unless";
+import when from "./when";
 
 export {
   add,
@@ -136,6 +137,7 @@ export {
   throwIf,
   toArray,
   unicodeToArray,
+  when,
 };
 
 export * from "./Lazy/index";
diff --git a/src/when.ts b/src/when.ts
new file mode 100644
index 00000000..fffdff27
--- /dev/null
+++ b/src/when.ts
@@ -0,0 +1,75 @@
+import isUndefined from "./isUndefined";
+
+/**
+ * It returns the original value based on the condition of the first argument or the result of executing the function passed as the second argument.
+ *
+ * @example
+ * ```typescript
+ * when(
+ *   isNumber,
+ *   () => `This is number`
+ *   100,
+ * ); // This is number
+ * ```
+ *
+ * with pipe
+ *
+ * ```typescript
+ * pipe(
+ *   100,
+ *   when(
+ *     isNumber,
+ *     () => `This is number`
+ *   )
+ * ) // This is number
+ *
+ * pipe(
+ *   100,
+ *   when(
+ *     isNumber,
+ *     (value) => value * 2
+ *   )
+ * ) // 200
+ *
+ * pipe(
+ *   100,
+ *   when(
+ *     isString,
+ *     () => `This is number` // not work
+ *   ),
+ * ) // 100
+ * ```
+ */
+
+function when<T, S extends T, R>(
+  predicate: (input: T) => input is S,
+  callback: (input: S) => R,
+): (iterator: T) => Exclude<T, S>;
+function when<T, R>(
+  predicate: (input: T) => boolean,
+  callback: (input: T) => R,
+): (iterator: T) => T | R;
+function when<T, S extends T, R>(
+  predicate: (input: T) => input is S,
+  callback: (input: S) => R,
+  iterator: T,
+): Exclude<T, S>;
+function when<T, R>(
+  predicate: (input: T) => boolean,
+  callback: (input: T) => R,
+  iterator: T,
+): T | R;
+
+function when<T, R>(
+  predicate: (input: T) => boolean,
+  callback: (input: T) => R,
+  iterator?: T,
+) {
+  if (isUndefined(iterator))
+    return (currentIterator: T) => when(predicate, callback, currentIterator);
+  if (predicate(iterator)) return callback(iterator);
+
+  return iterator;
+}
+
+export default when;


Test patch:
diff --git a/test/when.spec.ts b/test/when.spec.ts
new file mode 100644
index 00000000..1f606a74
--- /dev/null
+++ b/test/when.spec.ts
@@ -0,0 +1,37 @@
+import { isString, pipe, when } from "../src";
+
+describe("when", function () {
+  const test = <T>(value: T) =>
+    when(
+      (value) => value === 100,
+      (value) => {
+        expect(value).toBeTruthy();
+      },
+      value,
+    );
+  const withPipe = <T>(value: T) =>
+    pipe(
+      value,
+      test,
+      when(isString, () => "Hello fxts"),
+    );
+
+  it("If the input value is a number, it will be filtered out by the first 'when' function. And throw undefined.", () => {
+    const INPUT_VALUE = 100;
+
+    test(INPUT_VALUE);
+    withPipe(INPUT_VALUE);
+  });
+  it("If the input value is a string, it will be filtered out by the second 'when' function. And return value is 'Hello fxts'", () => {
+    const INPUT_VALUE = "Hello World";
+
+    expect(test(INPUT_VALUE)).toBe("Hello World");
+    expect(withPipe(INPUT_VALUE)).toBe("Hello fxts");
+  });
+  it("If nothing matches, all 'when' functions will be passed.", () => {
+    const INPUT_VALUE = [1, 2, 3, 4];
+
+    expect(test(INPUT_VALUE)).toEqual(INPUT_VALUE);
+    expect(withPipe(INPUT_VALUE)).toEqual(INPUT_VALUE);
+  });
+});
diff --git a/type-check/when.test.ts b/type-check/when.test.ts
new file mode 100644
index 00000000..a96d61ce
--- /dev/null
+++ b/type-check/when.test.ts
@@ -0,0 +1,28 @@
+import { isNumber, isString, sum, when } from "../src";
+import * as Test from "../src/types/Test";
+
+const { checks, check } = Test;
+
+const VALUE_1 = 100;
+const VALUE_2 = [1, 2, 3, 4];
+
+const check1 = when(isNumber, (value) => `${value}`, VALUE_1);
+const check2 = when(isString, (value) => `${value}`, VALUE_1);
+const check3 = when(
+  (list) => sum(list) >= 10,
+  (value) => `${value}`,
+  VALUE_2,
+);
+const check4 = when(
+  (list) => sum(list) > 10,
+  (value) => `${value}`,
+  VALUE_2,
+);
+
+checks([
+  check<typeof VALUE_1, 100, Test.Pass>(),
+  check<typeof check1, string, Test.Pass>(),
+  check<typeof check2, number, Test.Pass>(),
+  check<typeof check3, typeof VALUE_2 | string, Test.Pass>(),
+  check<typeof check4, typeof VALUE_2 | string, Test.Pass>(),
+]);