diff --git a/README.md b/README.md index a2fe33a..dea8b2d 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,31 @@ const rejectedTask = Task.resolve(new Error()); const rejectedPromise = toPromise(rejectedTask); ``` +### `tap` + +The `tap` _function_ can be found in other libraries. It takes a `callback` and returns a function that invokes the `callback` with the _parameter_ and pass it through. It's useful when you need to perform some side effect. Suppose you need to log the _resolved value_ at certain point of a `Task` _method chaining_. Without the `tap` _function_ you would probably do something like: + +```typescript +Task + .resolve(0) + // + .map(resolvedValue => { + console.log(resolvedValue); + return resolvedValue; + }) + // +``` + +You can't use an _inline function_ nor a _point-free_ style because you don't want the _log_ to change the `Task`'s _resolved value_, so you need to _return_ it. It's way more fancier and handy to just use the `tap` _function_: + +```typescript +Task + .resolve(0) + // + .map(tap(x => console.log(x))) + // +``` + ### `share` `task.pipe(share())` diff --git a/src/tap.test.ts b/src/tap.test.ts new file mode 100644 index 0000000..c95dd67 --- /dev/null +++ b/src/tap.test.ts @@ -0,0 +1,34 @@ +import { Task } from '@ts-task/task'; +import { jestAssertNever, assertFork } from './testing-utils'; +import { tap } from './tap'; + +describe('tap', () => { + it('`tap` calls the function and returns the argument', cb => { + // GIVEN: + // A callback, + const callback = jest.fn(); + // ...a value + const value = 3; + + // ...and a Task that is resolved with that value + const task = Task + .resolve(value) + + // WHEN: + // The task is mapped with `tap` and the callback + .map(tap(callback)) + ; + + // THEN: + task.fork( + jestAssertNever(cb), + assertFork(cb, resolvedValue => { + // The callback is called with the value + expect(callback).toBeCalledWith(value); + + // ...and the value is resolved itself. + expect(resolvedValue).toEqual(value); + }) + ); + }); +}); diff --git a/src/tap.ts b/src/tap.ts new file mode 100644 index 0000000..55b4065 --- /dev/null +++ b/src/tap.ts @@ -0,0 +1,11 @@ +/** + * Calls `fn` performing its side effects but discarding its return value and returning the input parameter instead. + * @param fn Unary function that performs side effects and whose return value will be discarded + * @returns "tapped" `fn` + */ +export const tap = (fn: (x: T) => any) => + (x: T) => { + fn(x); + return x; + } +; diff --git a/src/ts-task-utils.ts b/src/ts-task-utils.ts index cb5e39a..0d13e62 100644 --- a/src/ts-task-utils.ts +++ b/src/ts-task-utils.ts @@ -2,3 +2,4 @@ export * from './case-error'; export * from './to-promise'; export * from './operators/share'; export * from './is-instance-of'; +export * from './tap'; diff --git a/test/types/tap.ts b/test/types/tap.ts new file mode 100644 index 0000000..ccdf315 --- /dev/null +++ b/test/types/tap.ts @@ -0,0 +1,18 @@ +import { tap } from '../../src/tap'; + +// We will test that the function returned by `tap` mantains its parameter's type +// and also uses it as return type. + +// Given a function from number to another type +const stringifyNumber = (x: number) => x.toString(); // $ExpectType (x: number) => string + +// ...the tapped version goes from number to number +tap(stringifyNumber); // $ExpectType (x: number) => number + +/////////////////////////////////// + +// Given a function from boolean to another type +const yesOrNo = (x: boolean) => x ? 'Yes' : 'No'; // $ExpectType (x: boolean) => "Yes" | "No" + +// ...the tapped versions goes from boolean to boolean +tap(yesOrNo); // $ExpectType (x: boolean) => boolean