combinators-p

0.8.0 • Public • Published

combinators-p

Build Status

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

Promises/A+

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.

Creating new Promises
Transforming and combining Promises
Collections
Utility functions

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(+ 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(+ 1);
const g = x => of(+ 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(+ 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(+ 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(+ 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(+ y);
const g = (x, y, z) => of(+ 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(+ y);
const g = (w, x, y, z) => of(+ 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);

License

GPL 3.0 licensed

Package Sidebar

Install

npm i combinators-p

Weekly Downloads

5

Version

0.8.0

License

GPL-3.0

Last publish

Collaborators

  • critocrito