Fluture
A complete Fantasy Land compatible Future library.
npm install --save fluture
Requires a node 5.0.0 compatible environment
like modern browsers, transpilers or Node 5+
Usage
Using the low level, high performance method API:
const Future = require('fluture');
const program = file =>
Future.node(done => fs.readFile(file, 'utf8', done))
.chain(Future.encase(JSON.parse))
.map(x => x.name)
.fork(console.error, console.log);
program('package.json');
Or use the high level, fully curried, functional dispatch API
for function composition using composers like S.pipe
:
const {node, chain, encase, map, fork} = require('fluture');
const program = S.pipe([
file => node(fs.readFile.bind(fs, file, 'utf8')),
chain(encase(JSON.parse)),
map(x => x.name),
fork(console.error, console.log)
]);
program('package.json')
Motivation
Existing implementations of Future are a pain to debug. This library was made in
an effort to provide great error messages when something goes wrong. The
library also comes bundled with many async control utilities. To prevent
these features from coming at the cost of performance, Fluture was optimized to
operate at high performance. For an overview of differences between Fluture
and other Future implementations, look at this wiki article.
Documentation
Type signatures
Hindley-Milner type signatures are used to document functions. A list of
all types used within these signatures follows:
Constructors
Future :: ((a -> Void), (b -> Void) -> Void) -> Future a b
A (monadic) container which represents an eventual value. A lot like Promise but
more principled in that it follows the Fantasy Land algebraic JavaScript
specification.
const eventualThing = Future((reject, resolve) => {
setTimeout(resolve, 500, 'world');
});
eventualThing.fork(
console.error,
thing => console.log(`Hello ${thing}!`)
);
of :: b -> Future a b
A constructor that creates a Future containing the given value.
const eventualThing = Future.of('world');
eventualThing.fork(
console.error,
thing => console.log(`Hello ${thing}!`)
);
after :: Number -> b -> Future a b
A constructor that creates a Future containing the given value after n milliseconds.
const eventualThing = Future.after(500, 'world');
eventualThing.fork(console.error, thing => console.log(`Hello ${thing}!`));
cast :: Forkable a b -> Future a b
Cast any Forkable to a Future.
Future.cast(require('data.task').of('hello')).value(console.log);
encase :: (a -> !b | c) -> a -> Future b c
Creates a Future which resolves with the result of calling the given function
with the given value, or rejects with the error thrown by the function.
const data = '{"foo" = "bar"}';
const parseJson = Future.encase(JSON.parse);
parseJson('a').fork(console.error, console.log)
try :: (Void -> !a | b) -> Future a b
A constructor that creates a Future which resolves with the result of calling
the given function, or rejects with the error thrown by the given function.
Sugar for Future.encase(f, undefined)
.
const data = {foo: 'bar'}
Future.try(() => data.foo.bar.baz)
.fork(console.error, console.log)
node :: ((a, b -> Void) -> Void) -> Future a b
A constructor that creates a Future which rejects with the first argument given
to the function, or resolves with the second if the first is not present.
This is a convenience for NodeJS users who wish to easily obtain a Future from
a node style callback API.
Future.node(done => fs.readFile('package.json', 'utf8', done))
.fork(console.error, console.log)
parallel :: PositiveInteger -> [Future a b] -> Future a [b]
Creates a Future which when forked runs all Futures in the given array
in
parallel, ensuring no more than limit
Futures are running at once.
const tenFutures = Array.from(Array(10).keys()).map(Future.after(20));
Future.parallel(1, tenFutures).fork(console.error, console.log);
Future.parallel(5, tenFutures).fork(console.error, console.log);
Future.parallel(Infinity, tenFutures).fork(console.error, console.log);
If you want to settle all Futures, even if some may fail, you can use this in
combination with fold:
const fourInstableFutures = Array.from(Array(4).keys()).map(
i => Future(
(rej, res) => setTimeout(
() => Math.random() > 0.8 ? rej('failed') : res(i),
20
)
)
);
const stabalizedFutures = fourInstableFutures.map(Future.fold(S.Left, S.Right))
Future.parallel(2, stabalizedFutures).fork(console.error, console.log);
cache :: Future a b -> Future a b
Returns a Future which caches the resolution value of the given Future so that
whenever it's forked, it can load the value from cache rather than reexecuting
the chain.
const eventualPackage = Future.cache(
Future.node(done => {
console.log('Reading some big data');
fs.readFile('package.json', 'utf8', done)
})
);
eventualPackage.fork(console.error, console.log);
eventualPackage.fork(console.error, console.log);
Method API
fork :: Future a b ~> (a -> Void), (b -> Void) -> Void
Execute the Future (which up until now was merely a container for its
computation), and get at the result or error.
It is the return from Fantasy Land to the real world. This function best shows
the fundamental difference between Promises and Futures.
Future.of('world').fork(
err => console.log(`Oh no! ${err.message}`),
thing => console.log(`Hello ${thing}!`)
);
Future.reject(new Error('It broke!')).fork(
err => console.log(`Oh no! ${err.message}`),
thing => console.log(`Hello ${thing}!`)
);
map :: Future a b ~> (b -> c) -> Future a c
Map over the value inside the Future. If the Future is rejected, mapping is not
performed.
Future.of(1)
.map(x => x + 1)
.fork(console.error, console.log);
chain :: Future a b ~> (b -> Future a c) -> Future a c
FlatMap over the value inside the Future. If the Future is rejected, chaining is
not performed.
Future.of(1)
.chain(x => Future.of(x + 1))
.fork(console.error, console.log);
chainRej :: Future a b ~> (a -> Future a c) -> Future a c
FlatMap over the rejection value inside the Future. If the Future is
resolved, chaining is not performed.
Future.reject(new Error('It broke!')).chainRej(err => {
console.error(err);
return Future.of('All is good')
})
.fork(console.error, console.log)
ap :: Future a (b -> c) ~> Future a b -> Future a c
Apply the value in the Future to the value in the given Future. If the Future is
rejected, applying is not performed.
Future.of(x => x + 1)
.ap(Future.of(1))
.fork(console.error, console.log);
race :: Future a b ~> Future a b -> Future a b
Race two Futures against eachother. Creates a new Future which resolves or
rejects with the resolution or rejection value of the first Future to settle.
Future.after(100, 'hello')
.race(Future.after(50, 'bye'))
.fork(console.error, console.log)
or :: Future a b ~> Future a b -> Future a b
Logical or for Futures.
Returns a new Future which either resolves with the first resolutation value, or
rejects with the last rejection value once and if both Futures reject.
This behaves analogues to how JavaScript's or operator does, except both
Futures run simultaneously, so it is not short-circuited. That means that
if the second has side-effects, they will run even if the first resolves.
const result = planA().or(planB());
In the example, assume both plans return Futures. Both plans are executed in
parallel. If planA
resolves, the returned Future will resolve with its value.
If planA
fails there is always planB
. If both plans fail then the returned
Future will also reject using the rejection reason of planB
.
fold :: Future a b ~> (a -> c), (b -> c) -> Future _ c
Applies the left function to the rejection value, or the right function to the
resolution value, depending on which is present, and resolves with the result.
This provides a convenient means to ensure a Future is always resolved. It can
be used with other type constructors, like S.Either
, to maintain a
representataion of failures:
Future.of('hello')
.fold(S.Left, S.Right)
.value(console.log);
Future.reject('it broke')
.fold(S.Left, S.Right)
.value(console.log);
value :: Future a b ~> (b -> Void) -> Void
Extracts the value from a resolved Future by forking it. Only use this function
if you are sure the Future is going to be resolved, for example; after using
.fold()
. If the Future rejects and value
was used, an (likely uncatchable)
Error
will be thrown.
Future.reject(new Error('It broke'))
.fold(S.Left, S.Right)
.value(console.log)
promise :: Future a b ~> Promise b a
Cast the Future to a Promise. This will cause the Future to be eagerly forked.
This is useful if some API wants you to give it a Promise. You shouldn't need
this method for anything else.
Dispatcher API
fork :: (a -> Void) -> (b -> Void) -> Future a b -> Void
Dispatches the first and second arguments to the fork
method of the third argument.
const consoleFork = Future.fork(console.error, console.log);
consoleFork(Future.of('Hello'));
map :: Functor m => (a -> b) -> m a -> m b
Dispatches the first argument to the map
method of the second argument.
Has the same effect as R.map
.
chain :: Chain m => (a -> m b) -> m a -> m b
Dispatches the first argument to the chain
method of the second argument.
Has the same effect as R.chain
.
chainRej :: (a -> Future a c) -> Future a b -> Future a c
Dispatches the first argument to the chainRej
method of the second argument.
ap :: Apply m => m (a -> b) -> m a -> m b
Dispatches the second argument to the ap
method of the first argument.
Has the same effect as R.ap
.
race :: Future a b -> Future a b -> Future a b
Dispatches the first argument to the race
method of the second argument.
const first = futures => futures.reduce(Future.race);
first([
Future.after(100, 'hello'),
Future.after(50, 'bye'),
Future(rej => setTimeout(rej, 25, 'nope'))
])
.fork(console.error, console.log)
or :: Future a b -> Future a b -> Future a b
Dispatches the first argument to the or
method of the second argument.
const any = futures => futures.reduce(Future.or, Future.reject('Empty list!'));
any([
Future.reject('first: nope'),
Future.of('second: yep'),
Future.reject('third: yep')
])
.fork(console.error, console.log);
any([
Future.reject('first: nope'),
Future.reject('second: nope'),
Future.reject('third: nope')
])
.fork(console.error, console.log);
fold :: (a -> c) -> (b -> c) -> Future a b -> Future _ c
Dispatches the first and second arguments to the fold
method of the third argument.
value :: (b -> Void) -> Future a b -> Void
Dispatches the first argument to the value
method of the second argument.
Futurization
To reduce the boilerplate of making Node or Promise functions return Future's
instead, one might use the Futurize library:
const Future = require('fluture');
const futurize = require('futurize').futurize(Future);
const readFile = futurize(require('fs').readFile);
readFile('README.md', 'utf8')
.map(text => text.split('\n'))
.map(lines => lines[0])
.fork(console.error, console.log);
Road map
Benchmarks
Simply run node ./bench/<file>
to see how a specific method compares to
implementations in data.task
, ramda-fantasy.Future
and Promise
*.
* Promise is not included in all benchmarks because it tends to make the
process run out of memory.
The name
A conjunction of the acronym to Fantasy Land (FL) and Future. Also "fluture"
means butterfly in Romanian; A creature you might expect to see in Fantasy Land.
MIT licensed