From 5fa84dce951d119966f7cc1db7977e5a2c433ec0 Mon Sep 17 00:00:00 2001 From: Gonza Gluzman Date: Sun, 7 Oct 2018 03:16:28 -0300 Subject: [PATCH 1/2] feat(tap): added 'tap' function --- README.md | 25 +++++++++++++++++++++++++ src/tap.test.ts | 34 ++++++++++++++++++++++++++++++++++ src/tap.ts | 11 +++++++++++ src/ts-task-utils.ts | 1 + test/types/tap.ts | 18 ++++++++++++++++++ 5 files changed, 89 insertions(+) create mode 100644 src/tap.test.ts create mode 100644 src/tap.ts create mode 100644 test/types/tap.ts diff --git a/README.md b/README.md index a2fe33a..9a570f0 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 libreries. It just takes a `callback` _function_ and return a function that calls the `callback` and _returns_ the _argument_. 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(console.log)) + // +``` + ### `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 From e253c12cc19aa4b43514599fc5f479ca0e2ff68c Mon Sep 17 00:00:00 2001 From: Gonza Gluzman Date: Mon, 15 Oct 2018 17:59:48 -0300 Subject: [PATCH 2/2] chore(README): improved 'tap' documentation --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9a570f0..dea8b2d 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ const rejectedPromise = toPromise(rejectedTask); ### `tap` -The `tap` _function_ can be found in other libreries. It just takes a `callback` _function_ and return a function that calls the `callback` and _returns_ the _argument_. 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: +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 @@ -128,7 +128,7 @@ You can't use an _inline function_ nor a _point-free_ style because you don't wa Task .resolve(0) // - .map(tap(console.log)) + .map(tap(x => console.log(x))) // ```