Fluture offers a control structure similar to Promises, Tasks, Deferreds, and
what-have-you. Let's call them Futures.
Much like Promises, Futures represent the value arising from the success or
failure of an asynchronous operation (I/O). Though unlike Promises, Futures are
lazy and adhere to the monadic interface.
Some of the features provided by Fluture include:
For more information:
Usage
npm install --save fluture
On older environments you may need to polyfill one or more of the following
functions: Object.create
,
Object.assign
and Array.isArray
.
EcmaScript Module
Fluture is written as modular JavaScript (.mjs
). It can be loaded directly
by Node 9 and up using --experimental-modules
, with the esm loader, or
with TypeScript (typings included).
Besides the module system, no other ES5+ features are used in Fluture's source,
which means that no transpilation is needed after concatenation.
import {readFile} from 'fs';
import {node, encase} from 'fluture';
var getPackageName = file =>
node(done => { readFile(file, 'utf8', done) })
.chain(encase(JSON.parse))
.map(x => x.name);
getPackageName('package.json')
.fork(console.error, console.log);
CommonJS Module
Although the Fluture source uses the EcmaScript module system, versions
downloaded from the npm registry include a CommonJS build, which will
automatically be used when loading Fluture with require
.
var fs = require('fs');
var Future = require('fluture');
var getPackageName = function(file){
return Future.node(function(done){ fs.readFile(file, 'utf8', done) })
.chain(Future.encase(JSON.parse))
.map(function(x){ return x.name });
};
getPackageName('package.json')
.fork(console.error, console.log);
Global Bundle (CDN)
Fluture is hosted in full with all of its dependencies at
https://rawgit.com/fluture-js/Fluture/master/dist/bundle.js.
This script will add Fluture
to the global scope.
Interoperability
Future
implements Fantasy Land and Static Land -compatible
Bifunctor
, Monad
and ChainRec
(of
, ap
, map
, bimap
, chain
, chainRec
).
All versions of Fantasy Land are supported.Future.Par
implements Fantasy Land 3 and Static Land -compatible
Alternative
(of
, zero
, map
, ap
, alt
).- The Future and ConcurrentFuture representatives contain
@@type
properties
for Sanctuary Type Identifiers. - The Future and ConcurrentFuture instances contain
@@show
properties for
Sanctuary Show.
Butterfly
The name "Fluture" is a conjunction of "FL" (the acronym to Fantasy Land)
and "future". Fluture means butterfly in Romanian: A creature one might expect
to see in Fantasy Land.
Credit goes to Erik Fuente for styling the logo, and WEAREREASONABLEPEOPLE
for sponsoring the project.
Documentation
Table of contents
General
Creating new Futures
Converting between Nodeback APIs and Futures
Converting between Promises and Futures
Transforming and combining Futures
Consuming/forking Futures
Concurrency related utilities and data structures
Resource management and utilities
Type signatures
The various function signatures are provided in a small language referred to as
Hindley-Milner notation. Read about Hindley-Milner in JavaScript
here. On top of the basic Hindley-Milner notation, we use a few additions to
describe the JavaScript-specific stuff, like methods
or functions that take multiple arguments at once.
Squiggly Arrows
In order to document methods, we use the squiggly arrow (~>
). This
separates the implicit this
argument from the other, explicit, arguments.
For example, the following line signifies a method, as indicated by the
squiggly arrow:
Future.prototype.map :: Future a b ~> (b -> c) -> Future a c
For comparison, the following example shows a regular function:
map :: (b -> c) -> Future a b -> Future a c
Brackets
Most functions exposed by Fluture are curried. This is reflected in their type
signatures by using an arrow at each step where partial application is
possible. For example, the following line signifies a curried function,
because it has an arrow after each function argument:
add :: Number -> Number -> Number
We could have chosen to write the above line with "groups of one argument", but
we usually leave the grouping brackets out for brevity:
add :: (Number) -> (Number) -> Number
In order to document functions and methods that are not curried, we use
grouping to show which arguments have to be provided at the same time:
add :: (Number, Number) -> Number
Types
The concrete types you will encounter throughout this documentation:
- Future - Instances of Future provided by compatible versions of Fluture.
- ConcurrentFuture - Concurrified Futures (
Future.Par
). - Promise - Values which conform to the Promises/A+ specification.
- Nodeback a b - A Node-style callback; A function of signature
(a | Nil, b) -> x
. - Pair a b - An array with exactly two elements:
[a, b]
. - Iterator - Objects with
next
-methods which conform to the Iterator protocol. - Cancel - The nullary cancellation functions returned from computations.
- Catchable e f - A function
f
which may throw an exception e
.
Type classes
Some signatures contain constrained type variables.
Generally, these constraints express that some value must conform to a
Fantasy Land-specified interface.
Cancellation
Cancellation is a system whereby running Futures get an opportunity stop what
they're doing and release resources that they were holding, when the consumer
indicates it is no longer interested in the result.
To cancel a Future, it must be unsubscribed from. Most of the
consumption functions return an unsubscribe
function.
Calling it signals that we are no longer interested in the result. After
calling unsubscribe
, Fluture guarantees that our callbacks will not be
called; but more importantly: a cancellation signal is sent upstream.
The cancellation signal travels all the way back to the source (with the
exception of cached Futures - see cache
), allowing all parties
along the way to clean up.
With the Future
constructor, we can provide a the cancellation
handler by returning it from the computation. Let's see what this looks like:
var eventualAnswer = Future(function computeTheAnswer(rej, res){
var timeoutId = setTimeout(res, 60000, 42);
return function onCancel(){
clearTimeout(timeoutId);
};
});
var unsubscribe = eventualAnswer.fork(console.error, console.log);
unsubscribe();
Many natural sources in Fluture have cancellation handlers of their own.
after
, for example, does exactly what we've done just now: calling
clearTimeout
.
Finally, Fluture unsubscribes from Futures that it forks for us, when it no
longer needs the result. For example, both Futures passed into race
are forked, but once one of them produces a result, the other is unsubscribed
from, triggering cancellation. This means that generally, unsubscription and
cancellation is fully managed for us behind the scenes.
Stack safety
Fluture interprets our transformations in a stack safe way.
This means that none of the following operations result in a
RangeError: Maximum call stack size exceeded
:
var add1 = x => x + 1;
var m = Future.of(1);
for(var i = 0; i < 100000; i++){
m = m.map(add1);
}
m.fork(console.error, console.log);
var m = (function recur(x){
var mx = Future.of(x + 1);
return x < 100000 ? mx.chain(recur) : mx;
}(1));
m.fork(console.error, console.log);
To learn more about memory and stack usage under different types of recursion,
see (or execute) scripts/test-mem
.
Sanctuary
When using this module with Sanctuary Def (and Sanctuary by
extension) one might run into the following issue:
var S = require('sanctuary');
var Future = require('fluture');
S.I(Future.of(1));
This happens because Sanctuary Def needs to know about the types created by
Fluture to determine whether the type-variables are consistent.
To let Sanctuary know about these types, we can obtain the type definitions
from fluture-sanctuary-types
and pass them to S.create
:
var {create, env} = require('sanctuary');
var {env: flutureEnv} = require('fluture-sanctuary-types');
var Future = require('fluture');
var S = create({checkTypes: true, env: env.concat(flutureEnv)});
S.I(Future.of(1));
Casting Futures
Sometimes we may need to convert one Future to another, for example when the
Future was created by another package, or an incompatible version of Fluture.
When isFuture
returns false
, a conversion is necessary.
Usually the most concise way of doing this is as follows:
var NoFuture = require('incompatible-future');
var incompatible = NoFuture.of('Hello');
var compatible = Future(incompatible.fork.bind(incompatible));
compatible.both(Future.of('world')).value(console.log);
Creating Futures
Future
Future :: ((a -> Undefined, b -> Undefined) -> Cancel) -> Future a b
Future :: ((a -> Undefined, b -> Undefined) -> Cancel) -> Future a b
Creates a Future with the given computation. A computation is a function which
takes two callbacks. Both are continuations for the computation. The first is
reject
, commonly abbreviated to rej
; The second is resolve
, or res
.
When the computation is finished (possibly asynchronously) it may call the
appropriate continuation with a failure or success value.
Additionally, the computation may return a nullary function containing
cancellation logic. See Cancellation.
Future(function computation(reject, resolve){
setTimeout(resolve, 3000, 'world');
});
of
of :: b -> Future a b
of :: b -> Future a b
resolve :: b -> Future a b
Future.of :: b -> Future a b
Future.resolve :: b -> Future a b
Creates a Future which immediately resolves with the given value.
This function has an alias resolve
.
var eventualThing = Future.of('world');
eventualThing.fork(
console.error,
thing => console.log(`Hello ${thing}!`)
);
reject
reject :: a -> Future a b
reject :: a -> Future a b
Future.reject :: a -> Future a b
Creates a Future which immediately rejects with the given value.
var eventualFailure = Future.reject('I got so far!');
eventualFailure.fork(
e => console.error('I tried so hard!', e),
console.log
);
after
after :: Number -> b -> Future a b
after :: Number -> b -> Future a b
Creates a Future which resolves with the given value after the given number of
milliseconds.
var eventualThing = Future.after(500, 'world');
eventualThing.fork(console.error, thing => console.log(`Hello ${thing}!`));
rejectAfter
rejectAfter :: Number -> a -> Future a b
rejectAfter :: Number -> a -> Future a b
Creates a Future which rejects with the given reason after the given number of
milliseconds.
var eventualError = Future.rejectAfter(500, new Error('Kaputt!'));
eventualError.fork(err => console.log('Oh no - ' + err.message), console.log);
do
do :: (() -> Iterator) -> Future a b
do :: (() -> Iterator) -> Future a b
go :: (() -> Iterator) -> Future a b
A way to do async
/await
with Futures, similar to Promise Coroutines or
Haskell Do-notation.
Takes a function which returns an Iterator, commonly a
generator-function, and chains every produced Future over the previous.
This function has an alias go
, for environments where do
is reserved.
var eventualMessage = Future.do(function*(){
var thing = yield Future.after(300, 'world');
var message = yield Future.after(300, 'Hello ' + thing);
return message + '!';
});
eventualMessage.fork(console.error, console.log);
To handle errors inside a do
procedure, we need to fold
the error
into our control domain, I recommend folding into an Either
:
var attempt = Future.fold(S.Left, S.Right);
var ajaxGet = url => Future.reject('Failed to load ' + url);
var eventualMessage = Future.do(function*(){
var e = yield attempt(ajaxGet('/message'));
return S.either(
e => `Oh no! ${e}`,
x => `Yippee! ${x}`,
e
);
});
eventualMessage.fork(console.error, console.log);
try
try :: Catchable e (() -> r) -> Future e r
try :: Catchable e (() -> r) -> Future e r
attempt :: Catchable e (() -> r) -> Future e r
Creates a Future which resolves with the result of calling the given function,
or rejects with the error thrown by the given function.
Short for Future.encase(f, undefined)
.
This function has an alias attempt
, for environments where try
is reserved.
var data = {foo: 'bar'};
Future.try(() => data.foo.bar.baz)
.fork(console.error, console.log);
tryP
tryP :: (() -> Promise e r) -> Future e r
tryP :: (() -> Promise e r) -> Future e r
Create a Future which when forked spawns a Promise using the given function and
resolves with its resolution value, or rejects with its rejection reason.
Short for Future.encaseP(f, undefined)
.
Future.tryP(() => Promise.resolve('Hello'))
.fork(console.error, console.log);
node
node :: (Nodeback e r -> x) -> Future e r
node :: (Nodeback e r -> x) -> Future e r
Creates a Future which rejects with the first argument given to the function,
or resolves with the second if the first is not present.
Note that this function does not support cancellation.
Short for Future.encaseN(f, undefined)
.
Future.node(done => {
done(null, 'Hello');
})
.fork(console.error, console.log);
encase
encase :: (Catchable e (a -> r)) -> a -> Future e r
encase :: (Catchable e ((a ) -> r)) -> a -> Future e r
encase2 :: (Catchable e ((a, b ) -> r)) -> a -> b -> Future e r
encase3 :: (Catchable e ((a, b, c) -> r)) -> a -> b -> c -> Future e r
Takes a function and a value, and returns a Future which when forked calls the
function with the value and resolves with the result. If the function throws
an exception, it is caught and the Future will reject with the exception:
Partially applying encase
with a function f
allows us to create a "safe"
version of f
. Instead of throwing exceptions, the encased version always
returns a Future when given the remaining argument(s):
Furthermore; encase2
and encase3
are binary and ternary versions of
encase
, applying two or three arguments to the given function respectively.
var data = '{"foo" = "bar"}';
var safeJsonParse = Future.encase(JSON.parse);
safeJsonParse(data).fork(console.error, console.log);
encaseP
encaseP :: ((a) -> Promise e r) -> a -> Future e r
encaseP :: ((a) -> Promise e r) -> a -> Future e r
encaseP2 :: ((a, b) -> Promise e r) -> a -> b -> Future e r
encaseP3 :: ((a, b, c) -> Promise e r) -> a -> b -> c -> Future e r
Allows Promise-returning functions to be turned into Future-returning
functions.
Takes a function which returns a Promise, and a value, and returns a Future.
When forked, the Future calls the function with the value to produce the
Promise, and resolves with its resolution value, or rejects with its rejection
reason.
Furthermore; encaseP2
and encaseP3
are binary and ternary versions of
encaseP
, applying two or three arguments to the given function respectively.
var fetchf = Future.encaseP(fetch);
fetchf('https://api.github.com/users/Avaq')
.chain(res => Future.tryP(_ => res.json()))
.map(user => user.name)
.fork(console.error, console.log);
encaseN
encaseN :: ((a, Nodeback e r) -> x) -> a -> Future e r
encaseN :: ((a, Nodeback e r) -> x) -> a -> Future e r
encaseN2 :: ((a, b, Nodeback e r) -> x) -> a -> b -> Future e r
encaseN3 :: ((a, b, c, Nodeback e r) -> x) -> a -> b -> c -> Future e r
Allows continuation-passing-style functions to be turned into
Future-returning functions.
Takes a function which accepts as its last parameter a Nodeback, and
a value, and returns a Future. When forked, the Future calls the function with
the value and a Nodeback and resolves the second argument passed to the
Nodeback, or or rejects with the first argument.
Furthermore; encaseN2
and encaseN3
are binary and ternary versions of
encaseN
, applying two or three arguments to the given function respectively.
var fs = require('fs');
var read = Future.encaseN2(fs.readFile);
read('README.md', 'utf8')
.map(text => text.split('\n'))
.map(lines => lines[0])
.fork(console.error, console.log);
chainRec
Future.chainRec :: ((a -> Next a, b -> Done b, a) -> Future e (Next a | Done b), a) -> Future e b
Future.chainRec :: ((a -> Next a, b -> Done b, a) -> Future e (Next a | Done b), a) -> Future e b
Implementation of Fantasy Land ChainRec. Since Fluture 6.0
introduced stack safety there should be no need to use this
function directly. Instead it's recommended to use chain(rec)
.
Transforming Futures
map
map :: Functor m => (a -> b) -> m a -> m b
map :: Functor m => (a -> b) -> m a -> m b
Future.map :: Functor m => (a -> b) -> m a -> m b
Par.map :: Functor m => (a -> b) -> m a -> m b
Future.prototype.map :: Future e a ~> (a -> b) -> Future e b
Transforms the resolution value inside the Future, and returns a Future with
the new value. The transformation is only applied to the resolution branch: if
the Future is rejected, the transformation is ignored.
See also chain
and mapRej
.
Future.of(1)
.map(x => x + 1)
.fork(console.error, console.log);
For comparison, the equivalent with Promises is:
Promise.resolve(1)
.then(x => x + 1)
.then(console.log, console.error);
bimap
bimap :: Bifunctor m => (a -> c) -> (b -> d) -> m a b -> m c d
bimap :: Bifunctor m => (a -> c) -> (b -> d) -> m a b -> m c d
Future.bimap :: Bifunctor m => (a -> c) -> (b -> d) -> m a b -> m c d
Future.prototype.bimap :: Future a b ~> (a -> c, b -> d) -> Future c d
Maps the left function over the rejection value, or the right function over the
resolution value, depending on which is present.
Future.of(1)
.bimap(x => x + '!', x => x + 1)
.fork(console.error, console.log);
Future.reject('error')
.bimap(x => x + '!', x => x + 1)
.fork(console.error, console.log);
For comparison, the equivalent with Promises is:
Promise.resolve(1)
.then(x => x + 1, x => x + '!')
.then(console.log, console.error);
chain
chain :: Chain m => (a -> m b) -> m a -> m b
chain :: Chain m => (a -> m b) -> m a -> m b
Future.chain :: Chain m => (a -> m b) -> m a -> m b
Future.prototype.chain :: Future e a ~> (a -> Future e b) -> Future e b
Sequence a new Future using the resolution value from another. Similarly to
map
, chain
expects a function to transform the resolution value of
a Future. But instead of returning the new value, chain expects a Future to
be returned.
The transformation is only applied to the resolution branch: if the Future is
rejected, the transformation is ignored.
See also chainRej
.
Future.of(1)
.chain(x => Future.of(x + 1))
.fork(console.error, console.log);
For comparison, the equivalent with Promises is:
Promise.resolve(1)
.then(x => Promise.resolve(x + 1))
.then(console.log, console.error);
swap
swap :: Future a b -> Future b a
swap :: Future a b -> Future b a
Future.prototype.swap :: Future a b ~> Future b a
Resolve with the rejection reason, or reject with the resolution value.
Future.of(new Error('It broke')).swap().fork(console.error, console.log);
Future.reject('Nothing broke').swap().fork(console.error, console.log);
mapRej
mapRej :: (a -> c) -> Future a b -> Future c b
mapRej :: (a -> c) -> Future a b -> Future c b
Future.prototype.mapRej :: Future a b ~> (a -> c) -> Future c b
Map over the rejection reason of the Future. This is like map
,
but for the rejection branch.
Future.reject(new Error('It broke!'))
.mapRej(err => new Error('Oh No! ' + err.message))
.fork(console.error, console.log);
For comparison, the equivalent with Promises is:
Promise.resolve(1)
.then(null, err => Promise.reject(new Error('Oh No! ' + err.message)))
.then(console.log, console.error);
chainRej
chainRej :: (a -> Future c b) -> Future a b -> Future c b
chainRej :: (a -> Future c b) -> Future a b -> Future c b
Future.prototype.chainRej :: Future a b ~> (a -> Future c b) -> Future c b
Chain over the rejection reason of the Future. This is like
chain
, but for the rejection branch.
Future.reject(new Error('It broke!'))
.chainRej(err => Future.of(err.message + ' But it\'s all good.'))
.fork(console.error, console.log);
For comparison, the equivalent with Promises is:
Promise.reject(new Error('It broke!'))
.then(null, err => err.message + ' But it\'s all good.')
.then(console.log, console.error);
fold
fold :: (a -> c) -> (b -> c) -> Future a b -> Future d c
fold :: (a -> c) -> (b -> c) -> Future a b -> Future d c
Future.prototype.fold :: Future a b ~> (a -> c, b -> c) -> Future d 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 representation of failure.
Future.of('hello')
.fold(S.Left, S.Right)
.value(console.log);
Future.reject('it broke')
.fold(S.Left, S.Right)
.value(console.log);
For comparison, the equivalent with Promises is:
Promise.resolve('hello')
.then(S.Right, S.Left)
.then(console.log);
Combining Futures
ap
ap :: Apply m => m (a -> b) -> m a -> m b
ap :: Apply m => m (a -> b) -> m a -> m b
Future.ap :: Apply m => m (a -> b) -> m a -> m b
Par.ap :: Apply m => m (a -> b) -> m a -> m b
Future.prototype.ap :: Future e (a -> b) ~> Future e a -> Future e b
Applies the function contained in the left-hand Future or Apply to the value
contained in the right-hand Future or Apply. If one of the Futures rejects the
resulting Future will also be rejected.
Future.of(x => y => x + y)
.ap(Future.of(1))
.ap(Future.of(2))
.fork(console.error, console.log);
and
and :: Future a b -> Future a c -> Future a c
and :: Future a b -> Future a c -> Future a c
Future.prototype.and :: Future a b ~> Future a c -> Future a c
Logical and for Futures.
Returns a new Future which either rejects with the first rejection reason, or
resolves with the last resolution value once and if both Futures resolve. We
can use it if we want a computation to run only after another has succeeded.
See also or
and finally
.
Future.after(300, null)
.and(Future.of('hello'))
.fork(console.error, console.log);
With good old reduce
, we can turn this into an asynchronous all
function,
where the resulting Future will be the leftmost to reject, or the rightmost to
resolve.
var all = ms => ms.reduce(Future.and, Future.of(0));
all([Future.after(20, 1), Future.of(2)]).value(console.log);
or
or :: Future a b -> Future a b -> Future a b
or :: Future a b -> Future a b -> Future a b
Future.prototype.or :: Future a b ~> Future a b -> Future a b
Logical or for Futures.
Returns a new Future which either resolves with the first resolution value, or
rejects with the last rejection value once and if both Futures reject. We can
use it if we want a computation to run only if another has failed.
See also and
and finally
.
Future.rejectAfter(300, new Error('Failed'))
.or(Future.of('hello'))
.fork(console.error, console.log);
With good old reduce
, we can turn this into an asynchronous any
function,
where the resulting Future will be the leftmost to resolve, or the rightmost
to reject.
var any = ms => ms.reduce(Future.or, Future.reject('empty list'));
any([Future.reject(1), Future.after(20, 2), Future.of(3)]).value(console.log);
finally
finally :: Future a c -> Future a b -> Future a b
finally :: Future a c -> Future a b -> Future a b
lastly :: Future a c -> Future a b -> Future a b
Future.prototype.finally :: Future a b ~> Future a c -> Future a b
Future.prototype.lastly :: Future a b ~> Future a c -> Future a b
Run a second Future after the first settles (successfully or unsuccessfully).
Rejects with the rejection reason from the first or second Future, or resolves
with the resolution value from the first Future. We can use this when we want
a computation to run after another settles, successfully or unsuccessfully.
If you're looking to clean up resources after running a computation which
acquires them, you should use hook
, which has many more fail-safes
in place.
This function has an alias lastly
, for environments where finally
is
reserved.
See also and
and or
.
Future.of('Hello')
.finally(Future.of('All done!').map(console.log))
.fork(console.error, console.log);
Note that the first Future is given as the last argument to Future.finally()
:
var program = S.pipe([
Future.of,
Future.finally(Future.of('All done!').map(console.log)),
Future.fork(console.error, console.log)
]);
program('Hello');
Consuming Futures
fork
fork :: (a -> Any) -> (b -> Any) -> Future a b -> Cancel
fork :: (a -> Any) -> (b -> Any) -> Future a b -> Cancel
Future.prototype.fork :: Future a b ~> (a -> Any, b -> Any) -> Cancel
Execute the computation represented by a Future, passing reject
and resolve
callbacks to continue once there is a result.
This function is called fork
because it literally represents a fork in our
program: a point where a single code-path splits in two. It is recommended to
keep the number of calls to fork
at a minimum for this reason. The more
forks, the higher the code complexity.
Generally, one only needs to call fork
in a single place in the entire
program.
After we fork
a Future, the computation will start running. If the program
decides halfway through that it's no longer interested in the result of the
computation, it can call the unsubscribe
function returned by fork()
. See
Cancellation.
Note that if an exception was encountered during the computation, it will be
thrown and likely not be catchable. If the computation ran in isolation, we may
want to use forkCatch
instead to recover from exceptions.
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}!`)
);
var consoleFork = Future.fork(console.error, console.log);
consoleFork(Future.of('Hello'));
forkCatch
forkCatch :: (Error -> Any) -> (a -> Any) -> (b -> Any) -> Future a b -> Cancel
forkCatch :: (Error -> Any) -> (a -> Any) -> (b -> Any) -> Future a b -> Cancel
Future.prototype.forkCatch :: Future a b ~> (Error -> Any, a -> Any, b -> Any) -> Cancel
An advanced version of fork that allows us to recover in the event
that an error was throw
n during the executation of the computation.
The recovery function will always be called with an instance of Error,
independent of what was thrown.
Using this function is a trade-off;
Generally it's best to let a program crash and restart when an unexpected
exception occurs. Restarting is the surest way to restore the memory that was
allocated by the program to an expected state.
By using forkCatch
, we might be able to keep our program alive longer, which
can be very beneficial when the program is being used by multiple clients.
However, we also forego the certainty that our program will be in a valid state
after this happens. The more isolated the memory consumed by the particular
computation was, the more certain we will be that recovery is safe.
var fut = Future.after(300, null).map(x => x.foo);
fut.forkCatch(console.error, console.error, console.log);
value
value :: (b -> x) -> Future a b -> Cancel
value :: (b -> x) -> Future a b -> Cancel
Future.prototype.value :: Future a b ~> (b -> x) -> Cancel
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 uncatchable
Error
will be thrown.
Future.reject(new Error('It broke'))
.fold(S.Left, S.Right)
.value(console.log);
As with fork
, value
returns an unsubscribe
function. See
Cancellation.
done
done :: Nodeback a b -> Future a b -> Cancel
done :: Nodeback a b -> Future a b -> Cancel
Future.prototype.done :: Future a b ~> Nodeback a b -> Cancel
Fork the Future into a Nodeback.
This is like fork
, but instead of taking two unary functions, it
takes a single binary function.
As with fork
, done
returns an unsubscribe
function. See
Cancellation.
Future.of('hello').done((err, val) => console.log(val));
promise
promise :: Future a b -> Promise b a
promise :: Future a b -> Promise b a
Future.prototype.promise :: Future a b ~> Promise b a
An alternative way to fork
the Future. Returns a Promise which
resolves with the resolution value, or rejects with the rejection reason of
the Future.
Note that if an exception was encountered during the computation, it will be
thrown and likely not be catchable.
Future.of('Hello').promise().then(console.log);
This is a convenience function which provides a "quick and dirty" way to create
a Promise from a Future. You should only use it in scenarios where you're not
interested in cancellation, nor interested in recovering from
exceptions. For example in a test runner that wants you to give it a Promise.
In any other scenario, if you really want a Promise, you should probably
make a custom wrapper around forkCatch
to create your Promise,
for example:
const eventualThing = Future.after(300, 'World');
new Promise((res, rej) => {
const cancel = eventualThing.forkCatch(rej, reason => {
res({success: false, reason: reason, value: null});
}, value => {
res({success: true, reason: null, value: value});
});
process.on('SIGINT', cancel);
});
Parallelism
race
race :: Future a b -> Future a b -> Future a b
race :: Future a b -> Future a b -> Future a b
Future.prototype.race :: Future a b ~> Future a b -> Future a b
Race two Futures against each other. Creates a new Future which resolves or
rejects with the resolution or rejection value of the first Future to settle.
When one Future settles, the other gets cancelled automatically.
Future.after(100, 'hello')
.race(Future.after(50, 'bye'))
.fork(console.error, console.log);
With good old reduce
, we can turn this into a first
function, where the
resulting Future will be the first to resolve, or the first to reject.
var first = futures => futures.reduce(Future.race, Future.never);
first([
Future.after(100, 'hello'),
Future.after(50, 'bye'),
Future.rejectAfter(25, 'nope')
])
.fork(console.error, console.log);
both
both :: Future a b -> Future a c -> Future a (Pair b c)
both :: Future a b -> Future a c -> Future a (Pair b c)
Future.prototype.both :: Future a b ~> Future a c -> Future a (Pair b c)
Run two Futures in parallel and get a Pair
of the results. When
either Future rejects, the other Future will be cancelled and the resulting
Future will reject.
var a = Future.of('a');
var b = Future.of('b');
Future.both(a, b).fork(console.error, console.log);
parallel
parallel :: PositiveInteger -> Array (Future a b) -> Future a (Array b)
parallel :: PositiveInteger -> Array (Future a b) -> Future a (Array 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.
When one Future rejects, all currently running Futures will be cancelled and
the resulting Future will reject.
var 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:
var unstableFutures = Array.from({length: 4}, (_, i) =>
Future.node(done => done(Math.random() > 0.75 ? 'failed' : null, i))
);
var stabalizedFutures = unstableFutures.map(Future.fold(S.Left, S.Right));
Future.parallel(Infinity, stabalizedFutures).fork(console.error, console.log);
ConcurrentFuture
The ConcurrentFuture
type is the result of applying
concurrify
to Future
. It provides a mechanism for
constructing a Fantasy Land Alternative
from a member of
Future
. This allows Futures to benefit from the Alternative Interface, which
includes parallel ap
, zero
and alt
.
The idea is that we can switch back and forth between Future
and
ConcurrentFuture
, using Par
and seq
, to get sequential or
concurrent behaviour respectively. It's a useful type to pass to abstractions
that don't know about Future-specific functions like parallel
or
race
, but do know how to operate on Apply and Alternative.
var {of, ap, sequence} = require('sanctuary');
var {Future, Par, seq} = require('fluture');
var x = 1;
var f = a => a + 1;
var parx = of(Par, x);
var parf = Par(of(Future, f));
seq(ap(parx, parf)).value(console.log);
seq(sequence(Par, [parx, parf])).value(console.log);
Par
Converts a Future to a ConcurrentFuture.
Par :: Future a b -> ConcurrentFuture a b
Par :: Future a b -> ConcurrentFuture a b
Par.of
Constructs a ConcurrentFuture with the given resolution value.
Par.of :: b -> ConcurrentFuture a b
Par.of :: b -> ConcurrentFuture a b
Par.zero
Constructs a ConcurrentFuture which will never resolve or reject with anything.
Par.zero :: () -> ConcurrentFuture a a
Par.zero :: () -> ConcurrentFuture a a
seq
Converts a ConcurrentFuture to a Future.
seq :: ConcurrentFuture a b -> Future a b
seq :: ConcurrentFuture a b -> Future a b
alt
Select one of two Alts. In terms of the ConcurrentFuture
type, this means racing the two against one another with the same
semantics as race
.
alt :: Alt f => f a -> f a -> f a
alt :: Alt f => f a -> f a -> f a
Par.alt :: Alt f => f a -> f a -> f a
import {Future, Par, seq, alt} from 'fluture';
seq(alt(Par.zero, Par.of(1))).value(console.log);
seq(alt(Par(Future.after(20, 1)), Future.after(10, 2))).value(console.log);
Resource management
Functions listed under this category allow for more fine-grained control over
the flow of acquired values.
hook
hook :: Future a b -> (b -> Future a c) -> (b -> Future a d) -> Future a d
hook :: Future a b -> (b -> Future a c) -> (b -> Future a d) -> Future a d
Allows a Future-returning function to be decorated with resource acquisition
and disposal. The signature is like hook(acquire, dispose, consume)
, where
acquire
is a Future which might create connections, open file handlers, etc.
dispose
is a function that takes the result from acquire
and should be used
to clean up (close connections etc) and consume
also takes the result from
acquire
, and may be used to perform any arbitrary computations using the
resource. The resolution value of dispose
is ignored.
var withConnection = Future.hook(
openConnection('localhost'),
closeConnection
);
withConnection(
conn => query(conn, 'EAT * cakes FROM bakery')
)
.fork(console.error, console.log);
When a hooked Future is cancelled while acquiring its resource, nothing else
will happen. However, once the resource has been acquired, the dispose
Future is guaranteed to run.
In the case where the consumption Future settles, the disposal is forked and
awaited. If the disposal Future rejects or encounters an exception, it will
replace the state of the consumption Future. Otherwise the rejection reason or
resolution value of the consumption Future will determine the final outcome.
In the case where an exception is encountered during consumption, the disposal
Future will run, but its results are ignored.
In the case where the consumption or disposal Future is cancelled from outside,
the disposal Future will start and/or finish its computation, but the results
will be ignored.
Utility functions
pipe
pipe :: Future a b ~> (Future a b -> c) -> c
pipe :: Future a b ~> (Future a b -> c) -> c
A method available on all Futures to allow arbitrary functions over Futures to
be included in a fluent-style method chain.
This method is particularly useful in combination with functions derived from
Fantasy Land implementations, for example S.join
:
Future.of(42)
.map(Future.resolve)
.pipe(S.join)
.value(console.log);
cache
cache :: Future a b -> Future a b
cache :: Future a b -> Future a b
Returns a Future which caches the resolution value or rejection reason of the
given Future so that whenever it's forked, it can load the value from cache
rather than re-executing the underlying computation.
This essentially turns a unicast Future into a multicast Future, allowing
multiple consumers to subscribe to the same result. The underlying computation
is never cancelled unless all consumers unsubscribe before
it completes.
There is a glaring drawback to using cache
, which is that returned Futures
are no longer referentially transparent, making reasoning about them more
difficult and refactoring code that uses them harder.
var {readFile} = require('fs');
var eventualPackage = Future.cache(
Future.node(done => {
console.log('Reading some big data');
readFile('package.json', 'utf8', done);
})
);
eventualPackage.fork(console.error, console.log);
eventualPackage.fork(console.error, console.log);
isFuture
isFuture :: a -> Boolean
isFuture :: a -> Boolean
Returns true for Futures and false for everything else. This function
(and S.is
) also return true
for instances of Future that were
created within other contexts. It is therefore recommended to use this over
instanceof
, unless your intent is to explicitly check for Futures created
using the exact Future
constructor you're testing against.
var Future1 = require('/path/to/fluture');
var Future2 = require('/other/path/to/fluture');
var noop = () => {};
var m1 = Future1(noop);
Future1.isFuture(m1) === (m1 instanceof Future1);
var m2 = Future2(noop);
Future1.isFuture(m2) === (m2 instanceof Future1);
never
never :: Future a a
never :: Future a a
A Future that never settles. Can be useful as an initial value when reducing
with race
, for example.
isNever
isNever :: a -> Boolean
isNever :: a -> Boolean
Returns true
if the given input is a never
.
extractLeft :: Future a b -> Array a
extractLeft :: Future a b -> Array a
Future.prototype.extractLeft :: Future a b ~> () -> Array a
Returns an array whose only element is the rejection reason of the Future.
In many cases it will be impossible to extract this value; In those cases, the
array will be empty. This function is meant to be used for type introspection:
it is not the correct way to consume a Future.
extractRight :: Future a b -> Array b
extractRight :: Future a b -> Array b
Future.prototype.extractRight :: Future a b ~> () -> Array b
Returns an array whose only element is the resolution value of the Future.
In many cases it will be impossible to extract this value; In those cases, the
array will be empty. This function is meant to be used for type introspection:
it is not the correct way to consume a Future.
License
MIT licensed