From 2dc9a86114776f1dadd1e73326499589b1922e71 Mon Sep 17 00:00:00 2001 From: nusohiro Date: Mon, 3 Feb 2025 23:25:34 +0900 Subject: [PATCH] feat: add tap function --- benchmarks/function/tap.bench.ts | 26 ++++++++++++++++++++ docs/function/tap.mdx | 31 ++++++++++++++++++++++++ src/function/tap.ts | 30 +++++++++++++++++++++++ src/mod.ts | 1 + tests/function/tap.test.ts | 41 ++++++++++++++++++++++++++++++++ 5 files changed, 129 insertions(+) create mode 100644 benchmarks/function/tap.bench.ts create mode 100644 docs/function/tap.mdx create mode 100644 src/function/tap.ts create mode 100644 tests/function/tap.test.ts diff --git a/benchmarks/function/tap.bench.ts b/benchmarks/function/tap.bench.ts new file mode 100644 index 000000000..fb4cb1f80 --- /dev/null +++ b/benchmarks/function/tap.bench.ts @@ -0,0 +1,26 @@ +import * as _ from 'radashi' + +describe('tap', () => { + bench('executing a side-effect function on a single object', () => { + const obj = { a: 1 } + _.tap(obj, value => { + value.a += 1 + }) + }) + + bench('executing a side-effect function on a large array', () => { + const largeArray = Array(1000) + .fill(null) + .map((_, idx) => ({ id: idx, value: idx * 2 })) + _.tap(largeArray, array => { + array.forEach(item => (item.value += 1)) + }) + }) + + bench('executing a side-effect function on deeply nested objects', () => { + const nestedObj = { a: { b: { c: { d: { e: 100 } } } } } + _.tap(nestedObj, obj => { + obj.a.b.c.d.e += 1 + }) + }) +}) diff --git a/docs/function/tap.mdx b/docs/function/tap.mdx new file mode 100644 index 000000000..e43610d5b --- /dev/null +++ b/docs/function/tap.mdx @@ -0,0 +1,31 @@ +--- +title: tap +description: 'Execute a function with a given value and return the original value' +--- + +### Usage + +Pass in a value and a function. The function is executed with the given value, allowing for side effects or mutations. The original value is returned. + +```ts +import * as _ from 'radashi' + +const obj = { count: 0 } +_.tap(obj, value => { + value.count += 1 // Mutating the object +}) +console.log(obj) // => { count: 1 } + +const arr = [1, 2, 3] +_.tap(arr, value => { + value.push(4) // Mutating the array +}) +console.log(arr) // => [1, 2, 3, 4] + +// Non-mutating operations do not alter the original value +const num = 10 +_.tap(num, value => { + console.log(value * 2) // => 20 Side effect, but num remains unchanged +}) +console.log(num) // => 10 +``` diff --git a/src/function/tap.ts b/src/function/tap.ts new file mode 100644 index 000000000..9c0bb5a5f --- /dev/null +++ b/src/function/tap.ts @@ -0,0 +1,30 @@ +/** + * Executes a provided function with the given value and returns the original value. + * If the callback modifies the value using a mutating method (e.g., `push`, `splice` for arrays), + * those changes will persist. Otherwise, the original value remains unchanged. + * This is useful for performing side effects within method chains. + * + * @template T - The type of the value. + * @param {T} value - The value to be passed to the provided function. + * @param {(value: T) => void} fn - A function that receives the value and applies side effects or performs mutations. + * @returns {T} The original value. If `T` is an object or array and the callback modifies it using a mutating method, the changes will persist. + * + * @example + * // Example 1: Logging a value within a method chain + * const result = tap({ a: 1 }, value => console.log(value)); // Output: { a: 1 } + * console.log(result); // Output: { a: 1 } + * + * @example + * // Example 2: Modifying an object inside the callback + * const result = tap({ count: 0 }, value => value.count += 1); + * console.log(result); // Output: { count: 1 } + * + * @example + * // Example 3: Modifying an array inside the callback + * const result = tap([1, 2, 3], value => value.push(4)); + * console.log(result); // Output: [1, 2, 3, 4] + */ +export function tap(value: T, fn: (value: T) => void): T { + fn(value) + return value +} diff --git a/src/mod.ts b/src/mod.ts index 0a91c38d7..74d9137b6 100644 --- a/src/mod.ts +++ b/src/mod.ts @@ -60,6 +60,7 @@ export * from './function/always.ts' export * from './function/castComparator.ts' export * from './function/castMapping.ts' export * from './function/noop.ts' +export * from './function/tap.ts' export * from './number/clamp.ts' export * from './number/inRange.ts' diff --git a/tests/function/tap.test.ts b/tests/function/tap.test.ts new file mode 100644 index 000000000..fa1bea0e4 --- /dev/null +++ b/tests/function/tap.test.ts @@ -0,0 +1,41 @@ +import * as _ from 'radashi' + +describe('tap', () => { + it('executes the provided function with the given value', () => { + let calledValue = null + const obj = { a: 1 } + + _.tap(obj, value => { + calledValue = value + }) + + expect(calledValue).toBe(obj) + }) + + it('returns the original value', () => { + const obj = { a: 1 } + const result = _.tap(obj, () => {}) + + expect(result).toBe(obj) + }) + + it('allows mutation of the value inside the callback', () => { + const obj = { a: 1 } + + const result = _.tap(obj, value => { + value.a += 1 + }) + + expect(result).toEqual({ a: 2 }) + }) + + it('works with arrays and allows modifications inside the callback', () => { + const arr = [1, 2, 3] + + const result = _.tap(arr, value => { + value.push(4) + }) + + expect(result).toEqual([1, 2, 3, 4]) + }) +})