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.
Versions changelog.
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.
At this moment the following utilities are available:
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));
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));
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");
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");
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");
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");
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");
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");
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");
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");
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");