Security News
ESLint is Now Language-Agnostic: Linting JSON, Markdown, and Beyond
ESLint has added JSON and Markdown linting support with new officially-supported plugins, expanding its versatility beyond JavaScript.
A mathematically correct alternative to Promises for asynchronous control flow
Futures are containers which represent some eventual value as a result of an asynchronous computation, much like Promises. Unlike Promises, however, Futures are lazy and logical by design. They have a predictable API governed by the Fantasy Land algebraic JavaScript specification.
npm install --save fluture
Requires a node 5.0.0 compatible environment like modern browsers, transpilers or Node 5+
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');
//> "fluture"
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')
//> "fluture"
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. Some other features include:
Future.parallel
and Future#or
.For an overview of differences between Fluture and other Future implementations, have a look at this wiki article.
Hindley-Milner type signatures are used to document functions. A list of all types used within these signatures follows:
fork
method that takes at least two
arguments. This includes instances of Fluture, instances of Task from
data.task
or instances of Future from ramda-fantasy
.map
method which satisfies the
Fantasy Land Functor specification.chain
method which satisfies the
Fantasy Land Chain specification.ap
method which satisfies the
Fantasy Land Apply specification.Future :: ((a -> ()), (b -> ()) -> ?(() -> ())) -> 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}!`)
);
//> "Hello world!"
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}!`)
);
//> "Hello world!"
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}!`));
//> "Hello world!"
cast :: Forkable a b -> Future a b
Cast any Forkable to a Future.
Future.cast(require('data.task').of('hello')).value(console.log);
//> "hello"
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)
//> [SyntaxError: Unexpected token =]
try :: (() -> !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)
//> [TypeError: Cannot read property 'baz' of undefined]
node :: ((a, b -> ()) -> ()) -> 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));
//Runs all Futures in sequence:
Future.parallel(1, tenFutures).fork(console.error, console.log);
//after about 200ms:
//> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
//Runs upto five Futures in parallel:
Future.parallel(5, tenFutures).fork(console.error, console.log);
//after about 40ms:
//> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
//Runs all Futures in parallel:
Future.parallel(Infinity, tenFutures).fork(console.error, console.log);
//after about 20ms:
//> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
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);
//after about 40ms:
//> [ Right(0), Left("failed"), Right(2), Right(3) ]
fork :: Future a b ~> (a -> ()), (b -> ()) -> () -> ()
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}!`)
);
//> "Hello world!"
Future.reject(new Error('It broke!')).fork(
err => console.log(`Oh no! ${err.message}`),
thing => console.log(`Hello ${thing}!`)
);
//> "Oh no! It broke!"
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);
//> 2
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);
//> 2
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)
//> "All is good"
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);
//> 2
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)
//> "bye"
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.
//An asynchronous version of:
//const result = planA() || planB();
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);
//> Right('hello')
Future.reject('it broke')
.fold(S.Left, S.Right)
.value(console.log);
//> Left('it broke')
cache :: Future a b ~> Future a b
Returns a cached version of the Future so that whenever it's forked, it can load
the value from cache rather than reexecuting the chain. This means you can use
the same Future in multiple chain
s, without having to worry that it's going
to re-execute the computation every time.
Please note that cached Futures, and Futures derived from them do not automatically dispose of resources. When the cached Future is cancelled or disposed manually, it will clear its internal cache. The next time it's forked after that, it must re-execute the underlying Future to populate its cache again.
const eventualPackage = Future.node(done => {
console.log('Reading some big data');
fs.readFile('package.json', 'utf8', done)
})
.cache();
eventualPackage.fork(console.error, console.log);
//> "Reading some big data"
//> "{...}"
eventualPackage.fork(console.error, console.log);
//> "{...}"
value :: Future a b ~> (b -> ()) -> () -> ()
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)
//> Left([Error: It broke])
promise :: Future a b ~> Promise b a
An alternative way to fork
the Future. This eagerly forks the Future and
returns a Promise of the result. This is useful if some API wants you to give it
a Promise. It's the only method which forks the Future without a forced way to
handle the rejection branch, which means it's considered dangerous to use.
Future.of('Hello').promise().then(console.log);
//> "Hello"
fork :: (a -> ()) -> (b -> ()) -> Future a b -> () -> ()
Dispatches the first and second arguments to the fork
method of the third argument.
const consoleFork = fork(console.error, console.log);
consoleFork(of('Hello'));
//> "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(race);
first([
after(100, 'hello'),
after(50, 'bye'),
Future(rej => setTimeout(rej, 25, 'nope'))
])
.fork(console.error, console.log)
//> [Error nope]
or :: Future a b -> Future a b -> Future a b
Dispatches the first argument to the or
method of the second argument.
const program = S.pipe([
reject,
or(of('second chance')),
value(console.log)
]);
program('first chance')
> "second chance"
fold :: (a -> c) -> (b -> c) -> Future a b -> Future _ c
Dispatches the first and second arguments to the fold
method of the third argument.
cache :: Future a b -> Future a b
Dispatches to the cache
method.
const cachedConnection = Future.cache(mysqlConnect());
value :: (b -> ()) -> Future a b -> () -> ()
Dispatches the first argument to the value
method of the second argument.
promise :: Future a b -> Promise b a
Dispatches to the promise
method.
Future.promise(Future.after(300, 'Hello')).then(console.log);
//> "Hello"
When a Future is created, it is given the fork
function. This fork
function
sometimes creates resources, like setTimeout
s in the event loop or database
connections. In order to deal with the disposal of these resources, one may
return a function from fork
, which will be automatically called after the
Future has forked. This function is expected to be idempotent.
It's the responsibility of this fork
function to prevent rej
or res
from
being called after its returned disposal function has been called. All internal
Fluture functions, for example Future.after
, play by these rules.
const createDatabaseConnection = settings => Future((rej, res) => {
const conn = mysql.connect(settings, res);
return () => conn.hasEnded || conn.end();
});
createDatabaseConnection()
.chain(conn => conn.query('SELECT 1 + 1 AS two'))
//When we `fork`, all of the resource disposers will be automatically called.
.fork(console.error, console.log);
If you don't want a particular Future to automatically dispose of its resources,
you can construct it with false
passed as its second argument:
Future(
rej => {
const id = setTimeout(rej, 2000, 'timed out')
return () => clearTimeout(id);
},
false //<---
)
This Future, and any Future's derived from it through map
, chain
, etc. will
no longer automatically dispose of their resources after being forked. This
allows resources to only be disposed manually.
Both .fork()
and .value()
return the disposal function and you can call it
at any time to manually dispose of resources.
Besides resource disposal, this function can also be used to cancel running Futures. When called prematurely it will dispose the resources and cause the whole pipeline to come to a halt:
const loadPage = url => Future((rej, res) => {
const req = new XMLHttpRequest();
req.addEventListener('load', res);
req.addEventListener('error', rej);
req.open('GET', url);
req.send();
return () => (req.readyState < 4) && req.abort();
});
const cancel = loadPage('https://github.com').fork(onFailure, onSuccess);
//Cancel the Future immediately. Nothing will happen; `onFailure` nor
//`onSuccess` will ever be called anymore because `cancel` called `req.abort()`.
cancel();
To reduce the boilerplate of making Node or Promise functions return Futures 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);
//> "# Fluture"
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.
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.
FAQs
FantasyLand compliant (monadic) alternative to Promises
The npm package fluture receives a total of 26,170 weekly downloads. As such, fluture popularity was classified as popular.
We found that fluture demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 3 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
ESLint has added JSON and Markdown linting support with new officially-supported plugins, expanding its versatility beyond JavaScript.
Security News
Members Hub is conducting large-scale campaigns to artificially boost Discord server metrics, undermining community trust and platform integrity.
Security News
NIST has failed to meet its self-imposed deadline of clearing the NVD's backlog by the end of the fiscal year. Meanwhile, CVE's awaiting analysis have increased by 33% since June.