Skip to content

The library brings most of the familiar functional techniques (like functional composition) to asynchronous world with shining Promises

License

Notifications You must be signed in to change notification settings

Constantiner/fun-ctional

Repository files navigation

fun-ctional

Build Status codecov

With fun-ctional library it is possible to use most of the familiar functional techniques (like functional composition) in asynchronous world with shining Promises.

It allows to mix synchronous and asynchronous functions to produce reusable composable functions which compatible with all good old utilities from functional libraries like Lodash in terms of ability to use any synchronous functions. The only difference is that functions from fun-ctional library always return promises.

Changelog

Versions changelog.

Installation

Install it with NPM:

npm install @constantiner/fun-ctional

Note. The UMD version of package is ES5 compatible but you may need to use @babel/polyfill (for Symbol support) or even @babel/runtime (async/await support) with it.

Note. Build includes sourcemaps and minified versions of UMD files as well. You can find them in your node_modules/@constantiner/fun-ctional/browser folder.

Documentation

At this moment the following utilities are available:

acompose

Asynchronous compose function (acompose stays for async-compose).

The main purpose is to replace a Promise handling code like this:

somePromise.then(normalize).then(upperCase).then(insertGreetings);

with point-free style of functional compose syntax like the following:

acompose(insertGreetings, upperCase, normalize)(somePromise);

It is lazy and allows of reusing of promise handling chains.

First you need to import it:

import { acompose } from "@constantiner/fun-ctional";

Or:

const { acompose } = require("@constantiner/fun-ctional");

Or you can import it separately without the whole bundle:

import acompose from "@constantiner/fun-ctional/acompose";

Or:

const acompose = require("@constantiner/fun-ctional/acompose");

You can run acompose with Promise instance (for true asynchronous execution) or with any other object to use as usual functional composition. It produces a Promise and can be used in async/await context:

const message = await acompose(insertGreetings, upperCase, normalize)(somePromise);

It also allows to handle errors like for traditional Promise but only in the tail position of the chain:

acompose(insertGreetings, upperCase, normalize)(somePromise).catch(e => console.error(e));

apipe

Asynchronous pipe function (apipe stays for async-pipe).

The main purpose is to replace a Promise handling code like this:

somePromise.then(normalize).then(upperCase).then(insertGreetings);

with point-free style of functional pipe syntax like the following:

apipe(normalize, upperCase, insertGreetings)(somePromise);

It is lazy and allows of reusing of promise handling chains.

First you need to import it:

import { apipe } from "@constantiner/fun-ctional";

Or:

const { apipe } = require("@constantiner/fun-ctional");

Or you can import it separately without the whole bundle:

import apipe from "@constantiner/fun-ctional/apipe";

Or:

const apipe = require("@constantiner/fun-ctional/apipe");

You can run apipe with Promise instance (for true asynchronous execution) or with any other object to use as in usual functional composition. It produces a Promise and can be used in async/await context:

const message = await apipe(normalize, upperCase, insertGreetings)(somePromise);

It also allows to handle errors like for traditional Promise but only in the tail position of the chain:

apipe(normalize, upperCase, insertGreetings)(somePromise).catch(e => console.error(e));

amap

An asynchronous version of map over an iterable (amap stays for async-map).

It gets an iterable of values (or promises) as input (or a promise to resolve to iterable), resolves them, maps over map function and returns a promise which resolves to an array of values.

It allows asynchronous mapping point-free way and can be used with asynchronous compose functions.

It uses Promise.all() under the hood. So if mapping function is asynchronous (returns a promise) all promises are being generated at once and then resolved with Promise.all(). So if any of promises will produce error (promise rejection) all the other promises will be invoked. The advantage of this method of invoking promises it will finish earlier than sequential map (because of Promise.all()) but it may perform some fetches or even state modifications even in case of fail on some previous mapping steps.

See amapSeq for sequential implementation.

const [ first, second, third ] = await amap(getDataFromServer)([somePromise1, someValue2, somePromise3]);

Or even more traditional way:

amap(getDataFromServer)([somePromise1, someValue2, somePromise3])
	.then(values => console.log(values));

It first resolves a promises passed and then pass resolutions value to the mapping function.

Mapping function is called with three parameters: currentValue, currentIndex, array which are plain resolved values (not promises).

Input iterable's values are not restricted to promises but can be any value to pass as input to functions.

It also allows to handle errors like for traditional Promise:

amap(getDataFromServer)([somePromise1, someValue2, somePromise3]).catch(e => console.error(e));

Or you can use try/catch in async/await constructions.

Нou can use it with acompose or apipe:

const usersHtml = await acompose(getHtmlRepresentation, getUserNames, amap(getUser), getUserIds)(somePromise);

You can import it the following way:

import { amap } from "@constantiner/fun-ctional";

Or:

const { amap } = require("@constantiner/fun-ctional");

Or you can import it separately without the whole bundle:

import amap from "@constantiner/fun-ctional/amap";

Or:

const amap = require("@constantiner/fun-ctional/amap");

amapSeq

An asynchronous version of map over an iterable (amapSeq stays for async-map).

It gets an iterable of values (or promises) as input (or a promise to resolve to iterable), resolves them, maps over map function and returns a promise which resolves to an array of values.

It allows asynchronous mapping point-free way and can be used with asynchronous compose functions.

The difference from regular amap is if map function is asynchronous (returns a promise) every new invocation of map function performs sequentially after resolving previous promise. So if any of promises produces error (promise rejection) amapSeq will not produce new promises and they won't be invoked.

See amap for parallel implementation.

const [ first, second, third ] = await amapSeq(getDataFromServer)([somePromise1, someValue2, somePromise3]);

Or even more traditional way:

amapSeq(getDataFromServer)([somePromise1, someValue2, somePromise3])
	.then(values => console.log(values));

It first resolves a promises passed and then pass resolutions value to the mapping function.

Mapping function is called with three parameters: currentValue, currentIndex, array which are plain resolved values (not promises).

Input iterable's values are not restricted to promises but can be any value to pass as input to functions.

It also allows to handle errors like for traditional Promise:

amapSeq(getDataFromServer)([somePromise1, someValue2, somePromise3]).catch(e => console.error(e));

Or you can use try/catch in async/await constructions.

Нou can use it with acompose or apipe:

const usersHtml = await acompose(getHtmlRepresentation, getUserNames, amapSeq(getUser), getUserIds)(somePromise);

You can import it the following way:

import { amapSeq } from "@constantiner/fun-ctional";

Or:

const { amapSeq } = require("@constantiner/fun-ctional");

Or you can import it separately without the whole bundle:

import amapSeq from "@constantiner/fun-ctional/amapSeq";

Or:

const amapSeq = require("@constantiner/fun-ctional/amapSeq");

areduce

Asynchronous composable version of reduce method for iterables ("a" stays for "asynchronous").

It gets a list of values (or list of promises, or promise to resolve to list) and performs standard reduce on them.

Reduce function may be asynchronous to return a promise (to fetch some data etc). Initial value of reducer also could be a promise.

A sample usage is:

const sum = async (currentSum, invoiceId) => {
	const { total:invoiceTotal } = await fetchInvoiceById(invoiceId);
	return currentSum + invoiceTotal;
};

const paymentTotal = await areduce(sum, 0)(fetchInvoiceIds(userId));

Or the same with acompose:

const paymentTotal = await acompose(areduce(sum, 0), fetchInvoiceIds)(userId);

It takes a standard callback Function to execute on each element in the array, taking four standard arguments (accumulator, currentValue, currentIndex, array) and returns a function to accept input value (so it is composable).

You can import it the following way:

import { areduce } from "@constantiner/fun-ctional";

Or:

const { areduce } = require("@constantiner/fun-ctional");

Or you can import it separately without the whole bundle:

import areduce from "@constantiner/fun-ctional/areduce";

Or:

const areduce = require("@constantiner/fun-ctional/areduce");

areduceRight

Asynchronous composable version of reduce method for iterables ("a" stays for "asynchronous").

It gets a list of values (or list of promises, or promise to resolve to list) and performs standard reduce on them.

Reduce function may be asynchronous to return a promise (to fetch some data etc). Initial value of reducer also could be a promise.

A sample usage is:

const sum = async (currentSum, invoiceId) => {
	const { total:invoiceTotal } = await fetchInvoiceById(invoiceId);
	return currentSum + invoiceTotal;
};

const paymentTotal = await areduceRight(sum, 0)(fetchInvoiceIds(userId));

Or the same with acompose:

const paymentTotal = await acompose(areduceRight(sum, 0), fetchInvoiceIds)(userId);

It takes a standard callback Function to execute on each element in the array, taking four standard arguments (accumulator, currentValue, currentIndex, array) and returns a function to accept input value (so it is composable).

You can import it the following way:

import { areduceRight } from "@constantiner/fun-ctional";

Or:

const { areduceRight } = require("@constantiner/fun-ctional");

Or you can import it separately without the whole bundle:

import areduceRight from "@constantiner/fun-ctional/areduceRight";

Or:

const areduceRight = require("@constantiner/fun-ctional/areduceRight");

afilter

An asynchronous version of filter over an iterable (afilter stays for async-filter).

It gets an iterable of values (or promises) as input (or a promise to resolve to iterable), resolves them, filters over filter function (which returns boolean where true means current value will be included in resulting array) and returns a promise which resolves to an array of values (filtered input iterable).

It allows asynchronous filtering point-free way and can be used with asynchronous compose functions.

It uses Promise.all() under the hood. So if filtering function is asynchronous (returns a promise) all promises are being generated at once and then resolved with Promise.all(). So if any of promises will produce error (promise rejection) all the other promises will be invoked anyway. The advantage of this method of invoking promises it will finish earlier than sequential filter (because of Promise.all()) but it may perform some fetches or even state modifications even in case of fail on some previous filtering steps.

See afilterSeq for sequential implementation.

const [ first, third ] = await afilter(fetchPermissions)([somePromise1, someValue2, somePromise3]);

Or even more traditional way:

afilter(fetchPermissions)([somePromise1, someValue2, somePromise3])
	.then(values => console.log(values));

It first resolves a promises passed and then pass resolutions value to the filtering function.

Filtering function is called with three parameters: currentValue, currentIndex, array which are plain resolved values (not promises) and expects Boolean as return value (or a promise resolved to Boolean).

Input iterable's values are not restricted to promises but can be any value to pass as input to functions.

It also allows to handle errors like for traditional Promise:

afilter(fetchPermissions)([somePromise1, someValue2, somePromise3]).catch(e => console.error(e));

Or you can use try/catch in async/await constructions.

Нou can use it with acompose or apipe:

const usersHtml = await acompose(getHtmlRepresentation, getUserNames, afilter(fetchPermissions), getUserIds)(somePromise);

You can import it the following way:

import { afilter } from "@constantiner/fun-ctional";

Or:

const { afilter } = require("@constantiner/fun-ctional");

Or you can import it separately without the whole bundle:

import afilter from "@constantiner/fun-ctional/afilter";

Or:

const afilter = require("@constantiner/fun-ctional/afilter");

afilterSeq

An asynchronous version of filter over an iterable (afilterSeq stays for async-filter).

It gets an iterable of values (or promises) as input (or a promise to resolve to iterable), resolves them, filters over filter function (which returns boolean where true means current value will be included in resulting array) and returns a promise which resolves to an array of values (filtered input iterable).

It allows asynchronous filtering point-free way and can be used with asynchronous compose functions.

The difference from regular afilter is if filter function is asynchronous (returns a promise) every new invocation of filter function performs sequentially after resolving previous promise. So if any of promises produces error (promise rejection) afilterSeq will not produce new promises and they won't be invoked.

See afilter for parallel implementation.

const [ first, third ] = await afilterSeq(fetchPermissions)([somePromise1, someValue2, somePromise3]);

Or even more traditional way:

afilterSeq(fetchPermissions)([somePromise1, someValue2, somePromise3])
	.then(values => console.log(values));

It first resolves a promises passed and then pass resolutions value to the filtering function.

Filtering function is called with three parameters: currentValue, currentIndex, array which are plain resolved values (not promises) and expects Boolean as return value (or a promise resolved to Boolean).

Input iterable's values are not restricted to promises but can be any value to pass as input to functions.

It also allows to handle errors like for traditional Promise:

afilterSeq(fetchPermissions)([somePromise1, someValue2, somePromise3]).catch(e => console.error(e));

Or you can use try/catch in async/await constructions.

Нou can use it with acompose or apipe:

const usersHtml = await acompose(getHtmlRepresentation, getUserNames, afilterSeq(fetchPermissions), getUserIds)(somePromise);

You can import it the following way:

import { afilterSeq } from "@constantiner/fun-ctional";

Or:

const { afilterSeq } = require("@constantiner/fun-ctional");

Or you can import it separately without the whole bundle:

import afilterSeq from "@constantiner/fun-ctional/afilterSeq";

Or:

const afilterSeq = require("@constantiner/fun-ctional/afilterSeq");

applyFns

A kind of composable version of Promise.all().

It gets some value or promise as input, pass it to the functions list and produces the array of results after resolving all the functions which can return promises as well.

It allows to use Promise.all() point-free way:

const [ first, second ] = await applyFns(squareRoot, getDataFromServer)(somePromise);

Or:

const [ first, second ] = await applyFns(squareRoot, getDataFromServer)(25);

Or some more traditional way:

applyFns(squareRoot, getDataFromServer)(somePromise)
	.then(([ first, second ]) => [ second, first ]);

It first resolves a promise passed and then pass resolution value to all the functions.

Input value is not restricted to promise but can be any value to pass as input to functions.

It also allows to handle errors like for traditional Promise:

applyFns(squareRoot, getDataFromServer)(somePromise).catch(e => console.error(e));

or the same with async/await.

You can use it with acompose or apipe:

const userHtml = await acompose(getHtmlRepresentation, getFullName, applyFns(getFirstNameById, getLastNameById), getUserId)(somePromise);

You can import it the following way:

import { applyFns } from "@constantiner/fun-ctional";

Or:

const { applyFns } = require("@constantiner/fun-ctional");

Or you can import it separately without the whole bundle:

import applyFns from "@constantiner/fun-ctional/applyFns";

Or:

const applyFns = require("@constantiner/fun-ctional/applyFns");

acatch

Composable version of catch method for promises.

It gets a value (a promise or not), resolves it and if resulting promise was rejected, calls catch function passed.

It allows to handle errors within acompose or apipe asynchronous composition chains to restore broken state etc.

See applySafe for an option to invoke catch handler along with some mapping function.

A sample with acompose:

const resultOrFallback = await acompose(acatch(handleAndRecoverFn), canFailFn)(someInput);

Standalone usage:

const resultOrFallback = await acatch(handleAndRecoverFn)(requestDataAndReturnPromise());

It is the same as the following:

requestDataAndReturnPromise().catch(handleAndRecoverFn).then(resultOrFallback => console.log(resultOrFallback));

You can import it the following way:

import { acatch } from "@constantiner/fun-ctional";

Or:

const { acatch } = require("@constantiner/fun-ctional");

Or you can import it separately without the whole bundle:

import acatch from "@constantiner/fun-ctional/acatch";

Or:

const acatch = require("@constantiner/fun-ctional/acatch");

applySafe

Composable version of promise.then(mapFn).catch(catchFn).

It gets a value (a promise or not), resolves it and handles as promise.then(mapFn).catch(catchFn) returning resulting promise.

It allows to handle errors within acompose or apipe asynchronous composition chains to restore broken state etc.

See acatch for composable invocation of catch handler separately.

A sample with acompose:

const resultOrFallback = await acompose(applySafe(canFailFn, handleAndRecoverFn), canFailTooFn)(someInput);

Standalone usage:

const resultOrFallback = await applySafe(canFailFn, handleAndRecoverFn)(requestDataAndReturnPromise());

Here canFailFn is replacement for standard Promise's then method (which can reject) and handleAndRecoverFn for Promise's catch.

It is the same as the following:

requestDataAndReturnPromise().then(canFailFn).catch(handleAndRecoverFn).then(resultOrFallback => console.log(resultOrFallback));

Or even more complex example:

const resultOrFallback = await applySafe(acompose(handlerFn2, handlerFn1), handleAndRecoverFn)(requestDataAndReturnPromise());

You can import it the following way:

import { applySafe } from "@constantiner/fun-ctional";

Or:

const { applySafe } = require("@constantiner/fun-ctional");

Or you can import it separately without the whole bundle:

import applySafe from "@constantiner/fun-ctional/applySafe";

Or:

const applySafe = require("@constantiner/fun-ctional/applySafe");