combinators-p
v0.8.0
Published
Functions for monadic promises.
Downloads
27
Maintainers
Readme
combinators-p
combinators-p
allows to program with
Promises
in a functional style. It offers a collection of higher-order and utility
functions that operate on Promises.
It implements a monadic interface for Promises, but unlike other great libraries it doesn't introduce new semantics. It just forms a small wrapper around the native Promise API. This is great for integrating it into any codebase that already uses Promises, without having to relearn new semantics or changing the structure.
This library intends to be very lightweight. It has no external dependencies and has a size of less than 3K when minified and gzipped.
Synopsis
import {getJson, storeDb} from './async-utils';
import {flowP, tapP} from 'combinators-p';
const url = "https://url.horse/api";
const apiCall = flowP([getJson, tapP(console.log), storeDb]);
await apiCall(url);
Interoperability
combinators-p
is compatible with Promises/A+ and ES6 Promises.
It also implements Static Land
Functor
,
Bifunctor
,
Apply
,
Applicative
,
Chain
and
Monad
.
Contents
Usage
npm install --save combinators-p
Every function has an alias that appends P to the function name,
e.g. flowP
is an alias for flow
and collectP3
is an alias for
collect3
. This allows for cleaner imports in situations where function names
can clash.
import {map, sum} from "lodash/fp";
import {mapP} from "combinators-p";
map(sum, [1, 2, 3]); // Lodash version.
mapP(sum, [1, 2, 3]); // combinators-p version.
combinators-p
depends on [Array.isArray
][JS:Array.isArray]. You may need
to polyfill it if your JavaScript environment doesn't provide it.
map
: Map a function over a promise.bimap
: Map either the left or right function over a promise.ap
: Apply a function wrapped in a promise to a promisified value.chain
: Map a function over a promise.compose
: Compose two functions that return promises.whenElse
: Branch left if the predicate holds, otherwise branch right.when
: Conditionally call a function if the predicate holds.unlessElse
: Branch left if the predicate doesn't hold, otherwise branch right.unless
: Conditionally call a function if the predicate doesn't hold.
all
: Resolve all promises in an array.fold
: Reduce a list of values to a single value, using a reduction function.collect
: Map a function over every element of a list.flatmap
: Map a function over every element of a list and concatenate the results.
isPromise
: Determine whether value is a promise.tap
: Call a function for side effect and return the original value.tapClone
: Call a function for side effect and return the original value.caught
: Catch an exception on a promise and call a handler.spread
: Call a variadic function with the value of a promise as it's arguments.flow
: Compose functions into a chain.flow2
: Lift a composed function chain over two arguments.flow3
: Lift a composed function chain over three arguments.flow4
: Lift a composed function chain over four arguments.constant
: Create a function that always returns the same value.lift2
: Lift a binary function over two promises.lift3
: Lift a ternary function over three promises.lift4
: Lift a quatary function over four promises.delay
: Delay the resolution of a promise chain.retry
: Call an action, and retry it in case it fails.
API
of
Lift a value into a promise.
of :: b -> Promise a b
This is equivalent to Promise.resolve
. It returns a promise that resolves to
the applied value. This function is compliant with the Static Land
Applicative specification.
import {of} from "combinators-p";
const p = of(23);
p.then(x => console.log(`${x} things.`));
// Prints '23 things.'
reject
Create a rejected promise.
reject :: Promise p => a -> p a b
This function can either take an Error
object or an string. If a string is
provided, it is converted to an Error
.
import {reject} from "Combinators-p";
const msg = "Boom!";
reject(msg).catch(console.log);
// Prints `Error`
reject(new TypeError(msg)).catch(console.log);
// Prints `TypeError`
map
Map a function over a promise.
map :: Promise p => (a -> b) -> p a -> p b
It transforms the value that a promise resolves to and returns a new
promise. This is equivalent to promise.then(x => x + 1)
. The transformation
is only applied if the promise resolves successfully, it is ignored if the
promise gets rejected. This function is compliant with the Static Land
Functor specification.
import {of, map} from "combinators-p";
const p = of(1);
const f = x => x + 1;
map(f, p).then(console.log);
// Prints 2
bimap
Map either the left or right function over a promise.
bimap :: Promise p => (a -> c) -> (b -> d) -> p a b -> p c d
Map the left function over the rejection value, and the right function over the success value of a promise. This function is compliant with the Static Land Bifunctor specification.
import {of, bimap} from "combinators-p";
const f = () => console.log('Boom!');
const g = x => x + 1;
bimap(f, g, of(1)).then(console.log);
// Prints 2
bimap(f, g, Promise.reject());
// Prints 'Boom!'
ap
Apply a function wrapped in a promise to a promisified value.
ap :: Promise p => p (a -> b) -> p a -> p b
This function is compliant with the Static Land Apply specification.
import {of, ap} from "combinators-p";
const pf = of(v => v + 1);
const p = of(1);
ap(pf, p).then(console.log);
// Prints 2
chain
Map a function over a promise.
chain :: Promise p => (a -> p b) -> p a -> p b
This is equivalent to promise.then(f)
. In practice chain
works the same as
map
. The difference is only in the type. This function is compliant with the
Static Land Chain specification.
import {of, chain} from "combinators-p";
const f = x => of(x + 1);
chain(f, of(0)).then(consol.log);
// Prints 1
compose
Compose two functions that return promises.
compose :: Promise p => (a -> p b) -> (b -> p c) -> p a -> p c
compose
yields a third function that returns a promise. The resulting
composite function is denoted g∘f : X → Z
, defined by (g∘f)(x) = g(f(x))
for all x
in X
.
import {of, compose} from "combinators-p";
const f = x => of(x + 1);
const g = x => of(x + 5);
const h = compose(f, g);
h(10).then(console.log);
// Prints 16
whenElse
Branch left if the predicate holds, otherwise branch right.
whenElse :: Promise p => (p a -> Boolean) -> (p a -> p b) -> (p a -> p b) -> p c
This is a conditional branch like the builtin if ... else
construct.
import {whenElse} from "combinators-p";
const predicate = userExists;
const consequent = updateUser;
const alternative = createUser;
whenElse(predicate, consequent, alternative, user);
// Calls updateUser if the user exists, and otherwise creates it
when
Conditionally call a function if the predicate holds.
when :: Promise p => (p a -> Boolean) -> (p a -> p b) -> p c
This is a conditional branch like the builtin if
construct. If the predicate
returns true, it will return the result of the consequent, otherwise it
returns the original value.
import {when} from "combinators-p";
const pred = userExists;
const consequent = updateUser;
when(predicate, consequent, user);
// Calls updateUser if the user exists, otherwise returns the user
unlessElse
Branch left if the predicate doesn't hold, otherwise branch right.
unlexxElse :; Promise p => (p a -> Boolean) -> (p a -> p b) -> (p a -> p b) -> p c
This is a conditional branch like the builtin if (! ... ) ... else
construct.
import {unlessElse} from "combinators-p";
const predicate = userExists;
const consequent = createUser;
const alternative = createUser;
unlessEles(predicate, consequent, alternative, user);
// Creates the user unless it exists, otherwise updates it
unless
Conditionally call a function if the predicate doesn't hold.
unless :: Promise p => (p a -> Boolean) -> (p a -> p b) -> p c
This is a conditional branch like the builtin if (! ...)
construct. If the
predicate returns false, it will return the result of the consequent,
otherwise it returns the original value.
import {unless} from "combinators-p";
const pred = userExists;
const consequent = createUser;
unless(predicate, consequent, user);
// Calls createUser if the user doesn't exist, otherwise returns the user
all
Resolve all promises in an array.
all :: Promise p => [p b a] -> p b [a]
This is equivalent to Promise.all
, with the difference that it creates a
callable function.
import {all} from "combinators-p";
const f = all([openFile1(), opeFile2(), openFile3()]);
f().then(console.log);
// Prints [a, b, c]
fold
Reduce a list of values to a single value, using a reduction function.
fold :: Promise p => (p b c -> p b a -> p b c) -> p b c -> [p b a] -> p b c
This is equivalent to Array.reduce
.
import {of, fold} from "combinators-p";
const f = (acc, x) => of(acc + x);
const xs = [...Array(5).keys()];
fold(f, 0, xs).then(console.log);
// Prints 10
collect
Map a function over every element of a list.
collect :: Promise p => (p b a -> p b a) -> [p b a] -> p b [a]
This is equivalent to Array.map
. In it's standard version it only resolves
one promise at a time. There are specialized versions to resolve multiple
promises at the same time.
collect2
: Resolve two promises at the same time.collect3
: Resolve three promises at the same time.collect4
: Resolve four promises at the same time.collect5
: Resolve five promises at the same time.
import {of, collect} from "combinators-p";
const f = x => of(x + 1);
const xs = [...Array(5).keys()];
collect(f, xs).then(console.log);
// Prints [1, 2, 3, 4, 5]
flatmap
Map a function over every element of a list and concatenate the results.
flatmap :: Promise p => (p b a -> p b [a]) -> [p b a] -> p b [a]
This is equivalent to calling collect
and flattening the resulting list of
lists into a single list. In it's standard version it only resolves one
promise at a time. There are specialized versions to resolve multiple promises
at the same time.
collect2
: Resolve two promises at the same time.collect3
: Resolve three promises at the same time.collect4
: Resolve four promises at the same time.collect5
: Resolve five promises at the same time.
import {flatmap} from "combinators-p";
const f = x => [x, x];
const xs = [1, 2];
flatmap(f, xs).then(console.log);
// Prints [1, 1, 2, 2]
isPromise
Determine whether an object is a promise.
isPromise :: a -> Boolean
import {of, isPromise} from "combinators-p";
const p = of(23);
isPromise(p);
// Prints true
tap
Call a function for side effect and return the original value.
tap :: Promise p => (p b a -> ()) -> p b a -> p b a
import {of, flow, tap} from "combinators-p";
const f = a => of(a);
flow([f, tap(console.log)])(23);
// Print "23"
tapClone
Call a function for side effect and return the original value.
tap :: Promise p => (p b a -> ()) -> p b a -> p b a
This function is like tap
, but makes a deep clone of the value before
applying it to the function.
import {of, flow, tapClone} from "combinators-p";
const f = a => of(a);
flow([f, tapClone(console.log)])(23);
// Print "23"
caught
Catch an exception on a promise and call a handler.
caught :: Promise p => (p b -> p b a) -> p b -> p b a
This is equivalent to Promise.catch
.
import {caught, flow} from "combinators-p";
const f = () => new Error("Boom");
flow([f, caught(console.err)]);
// Prints the exception
spread
Call a variadic function with the value of a promise as it's arguments.
spread :: Promise p => (a -> b) -> p b [a] -> p b a
If the value is an array, flatten it to the formal parameters of the fulfillment handler.
import {of, flow, spread} from "combinators-p";
const plus = (x, y) => x + y;
const p = of([1, 2]);
spread(plus, p).then(console.log);
// Prints 3
flow
Compose functions into a chain.
flow :: Promise p => [(a -> c)] -> p b a -> p b a
Create a function out of a list of functions, where each successive invocation is supplied the return value of the previous function call. The new function forms a pipe where the results flow from left to right so to speak. It's a shortcut for composing more than two functions.
import {of, flow} from "combinators-p";
const f = (x, y) => of(x + y);
const fs = [...Array(5).keys()].map(f);
flow(fs, 0).then(console.log);
// Prints 10
flow2
Lift a composed function chain over two arguments.
flow :: Promise p => [(a -> a -> a) (a -> a)] -> p b a -> p b a -> p b a
This function works like flow
, but it accepts two arguments, that are lifted
into the first function of the chain.
import {of, flow2} from "combinators-p";
const f = (x, y) => of(x + y);
const fs = [...Array(5).keys()].map(f);
flow([f, ...fs], 0, 0).then(console.log);
// Prints 10
flow3
Lift a composed function chain over three arguments.
flow :: Promise p => [(a -> a -> a -> a) (a -> a)] -> p b a -> p b a -> p b a -> p b a
This function works like flow
, but it accepts three arguments, that are lifted
into the first function of the chain.
import {of, flow3} from "combinators-p";
const f = (x, y) => of(x + y);
const g = (x, y, z) => of(x + y + z);
const fs = [...Array(5).keys()].map(f);
flow([g, ...fs], 0, 0, 0).then(console.log);
// Prints 10
flow4
Lift a composed function chain over four arguments.
flow :: Promise p => [(a -> a -> a -> a ->) (a -> a)] -> p b a -> p b a -> p b a -> p b a -> p b a
This function works like flow
, but it accepts four arguments, that are lifted
into the first function of the chain.
import {of, flow4} from "combinators-p";
const f = (x, y) => of(x + y);
const g = (w, x, y, z) => of(w + x + y + z);
const fs = [...Array(5).keys()].map(f);
flow([g, ...fs], 0, 0, 0, 0).then(console.log);
// Prints 10
constant
Create a function that always returns the same value.
constant :: a -> (b -> Promise a)
import {constant} from "combinators-p";
const f = constant("Hello");
f().then(console.log);
// Prints "Hello"
lift2
Lift a binary function over two promises.
lift2 :: Promise p => (a -> a -> a) -> p b a -> p b a -> p b a
import {of, lift2} from "combinators-p";
const f = (x, y) => x + y;
lift2(f, of(1), of(2)).then(console.log);
// Prints 3
lift3
Lift a ternary function over three promises.
lift3 :: Promise p => (a -> a -> a -> a) -> p b a -> p b a -> p b a -> p b a
import {of, lift3} from "combinators-p";
const f = (x, y, z) => x + y + z;
lift3(f, of(1), of(2), of(3)).then(console.log);
// Prints 6
lift4
Lift a quartary function over four promises.
lift4 :: Promise p => (a -> a -> a -> a -> a) -> p b a -> p b a -> p b a -> p b a -> p b a
import {of, lift4} from "combinators-p";
const f = (w, x, y, z) => w + x + y + z;
lift4(f, of(1), of(2), of(3), of(4)).then(console.log);
// Prints 10
delay
Delay the resolution of a promise chain.
delay :: Promise p => x -> p b a -> p b a
The first arguments is the delay in milliseconds.
import {of, delay} from "combinators-p";
delay(100, of(23)).then(console.log);
// Waits 100 ms and print 23.
retry
Call an action, and retry it in case it fails.
retry :: Promise p => p b a -> p b a
An action is retried up to five times with an increasing timeout. The action can be a function as well. In it's standard version, the action function doesn't receive any arguments.
import {retry} from "combinators-p";
// Retries `fetchUser` in case of failure.
retry(fetchUser).then(console.log).catch(console.error);