Skip to content

🦋 FantasyLand compliant (monadic) alternative to Promises

License

Notifications You must be signed in to change notification settings

mmmveggies/Fluture

 
 

Repository files navigation

Fluture

Chat NPM Version Dependencies Build Status Code Coverage

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

Fluture is written for EcmaScript version 5.

For older environments you may need to polyfill one or more of the following functions: Object.create, Object.assign and Array.isArray. Fluture will tell you when this is needed.

CommonJS Module

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);
//> "fluture"

EcmaScript Module

The package.json sets a module-field for build-tools like Rollup. The module version also has TypeScript definitions.

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);
//> "fluture"

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

Fantasy Land Static Land

  • 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.

Butterfly

The name "Fluture" is a conjunction of "FL" (the acronym to Fantasy Land) and "future". Fluture means butterfly in Romanian: A creature you might expect to see in Fantasy Land.

Thanks to Erik Fuente for 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

Hindley-Milner type signatures are used to document functions. You might encounter some additional syntax that we use to describe 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 function, because it doesn't have a squiggly arrow in its signature:

map :: (b -> c) -> Future a b -> Future a c

Whereas the next example is a method. It needs a this as indicated by the use of the squiggly arrow.

Future.prototype.map :: Future a b ~> (b -> c) -> 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

You'll find that some signatures refer to concrete types, such as Future. This is reference of the types used throughout the 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". These constraints are expressed by means of type classes, specifically those defined by Fantasy Land and verified by Sanctuary Type Classes:

Stack safety

Fluture interprets your transformations in a stack safe way. This means that none of the following operations raise 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);
//> 100001
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);
//> 100001

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) you might run into the following issue:

var S = require('sanctuary');
var Future = require('fluture');
S.I(Future.of(1));
//! Since there is no type of which all the above values are members,
//! the type-variable constraint has been violated.

This happens because Sanctuary Def needs to know about the types created by Fluture to determine whether the type-variables are consistent.

To var 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));
//> Future.of(1)

Casting Futures

Sometimes you 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');

//Cast the incompatible Future to our version of Future:
var compatible = Future(incompatible.fork.bind(incompatible));

compatible.both(Future.of('world')).value(console.log);
//> ["Hello", "world"]

Creating Futures

Future

Future :: ((a -> x, b -> x) -> Cancel) -> Future a b
Future :: ((a -> x, b -> x) -> 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 resolve, which abbreviates to res. When the computation is finished (possibly asynchronously) it may call the appropriate continuation with a failure or success value.

Future(function computation(reject, resolve){
  //Asynchronous work:
  var x = setTimeout(resolve, 3000, 'world');
  //Cancellation:
  return () => clearTimeout(x);
});

Additionally, the computation may return a nullary function containing cancellation logic. This function is executed when the Future is cancelled after it's forked.

of

of :: b -> Future a b
of        :: b -> Future a b
Future.of :: b -> Future a b

Creates a Future which immediately resolves with the given value. This function is compliant with the Fantasy Land Applicative specification.

var eventualThing = Future.of('world');
eventualThing.fork(
  console.error,
  thing => console.log(`Hello ${thing}!`)
);
//> "Hello world!"

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. Just like of but for the rejection branch.

after

after :: Number -> b -> Future a b
after :: Number -> b -> Future a b

Creates a Future which resolves with the given value after n milliseconds.

var eventualThing = Future.after(500, 'world');
eventualThing.fork(console.error, thing => console.log(`Hello ${thing}!`));
//> "Hello world!"

rejectAfter

rejectAfter :: Number -> a -> Future a b
rejectAfter :: Number -> a -> Future a b

Creates a Future which rejects with the given reason after n milliseconds.

var eventualError = Future.rejectAfter(500, new Error('Kaputt!'));
eventualError.fork(err => console.log('Oh no - ' + err.message), console.log);
//! Oh no - Kaputt!

do

do :: (() -> Iterator) -> Future a b
do :: (() -> Iterator) -> Future a b
go :: (() -> Iterator) -> Future a b

A specialized version of fantasy-do which works only for Futures, but has the advantage of type-checking and not having to pass Future.of. Another advantage is that the returned Future can be forked multiple times, as opposed to with a general fantasy-do solution, where forking the Future a second time behaves erroneously.

Takes a function which returns an Iterator, commonly a generator-function, and chains every produced Future over the previous.

This allows for writing sequential asynchronous code without the pyramid of doom. It's known as "coroutines" in Promise land, and "do-notation" in Haskell land.

Future.do(function*(){
  var thing = yield Future.after(300, 'world');
  var message = yield Future.after(300, 'Hello ' + thing);
  return message + '!';
})
.fork(console.error, console.log);
//After 600ms:
//> "Hello world!"

Error handling is slightly different in do-notation, you need to fold the error into your 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);
Future.do(function*(){
  var e = yield attempt(ajaxGet('/message'));
  return S.either(
    e => `Oh no! ${e}`,
    x => `Yippee! ${x}`,
    e
  );
})
.fork(console.error, console.log);
//> "Oh no! Failed to load /message"

This function has an alias go, for environments in which do is a reserved word.

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).

var data = {foo: 'bar'};
Future.try(() => data.foo.bar.baz)
.fork(console.error, console.log);
//> [TypeError: Cannot read property 'baz' of undefined]

This function has an alias attempt, for environments in which try is a reserved word.

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);
//> "Hello"

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.

This is very similar to the Future()-constructor, except that it takes a single function with two arguments instead of two functions with a single argument.

Short for Future.encaseN(f, undefined).

Future.node(done => {
  done(null, 'Hello');
})
.fork(console.error, console.log);
//> "Hello"

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:

var data = '{"foo" = "bar"}';
Future.encase(JSON.parse, data)
.fork(console.error, console.log);
//! [SyntaxError: Unexpected token =]

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):

var data = '{"foo" = "bar"}';
var safeJsonParse = Future.encase(JSON.parse);
safeJsonParse(data).fork(console.error, console.log);
//! [SyntaxError: Unexpected token =]

Furthermore; encase2 and encase3 are binary and ternary versions of encase, applying two or three arguments to the given function respectively.

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.

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);
//> "Aldwin Vlasblom"

Furthermore; encaseP2 and encaseP3 are binary and ternary versions of encaseP, applying two or three arguments to the given function respectively.

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.

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);
//> "# [![Fluture](logo.png)](#butterfly)"

Furthermore; encaseN2 and encaseN3 are binary and ternary versions of encaseN, applying two or three arguments to the given function respectively.

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 new Future with the transformed value. This is like doing promise.then(x => x + 1), except that it's lazy, so the transformation will not be applied before the Future is forked. The transformation is only applied to the resolution branch: If the Future is rejected, the transformation is ignored. To learn more about the exact behaviour of map, check out its spec.

Future.of(1)
.map(x => x + 1)
.fork(console.error, console.log);
//> 2

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);
//> 2

Future.reject('error')
.bimap(x => x + '!', x => x + 1)
.fork(console.error, console.log);
//> "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

Allows the creation of a new Future based on the resolution value. This is like doing promise.then(x => Promise.resolve(x + 1)), except that it's lazy, so the new Future will not be created until the other one is forked. The function is only ever applied to the resolution value; it's ignored when the Future was rejected. To learn more about the exact behaviour of chain, check out its spec.

Future.of(1)
.chain(x => Future.of(x + 1))
.fork(console.error, console.log);
//> 2

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);
//! [It broke]

Future.reject('Nothing broke').swap().fork(console.error, console.log);
//> "Nothing broke"

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('Some extra info: ' + err.message))
.fork(console.error, console.log);
//! [Some extra info: It broke!]

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 => {
  console.error(err);
  return Future.of('All is good');
})
.fork(console.error, console.log);
//> "All is good"

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 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')

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);
//> 3

Note that even though #ap does not conform to the latest spec, the hidden fantasy-land/ap-method does. Therefore Future remains fully compliant to Fantasy Land.

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. This behaves analogously to how JavaScript's and-operator does.

//An asynchronous version of:
//isResolved() && getValue();
isResolved().and(getValue());
//Asynchronous "all", where the resulting Future will be the leftmost to reject:
var all = ms => ms.reduce(Future.and, Future.of(true));
all([Future.after(20, 1), Future.of(2)]).value(console.log);
//> 2

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. This behaves analogously to how JavaScript's or-operator does.

//An asynchronous version of:
//planA() || planB();
planA().or(planB());
//Asynchronous "any", where the resulting Future will be the leftmost to resolve:
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);
//> 2

Consuming Futures

fork

fork :: (a -> x) -> (b -> x) -> Future a b -> Cancel
fork                  ::               (a -> x) -> (b -> x) -> Future a b -> Cancel
Future.prototype.fork :: Future a b ~> (a -> x,     b -> x)               -> Cancel

Execute the computation that was passed to the Future at construction using the given reject and resolve callbacks.

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!"

var consoleFork = Future.fork(console.error, console.log);
consoleFork(Future.of('Hello'));
//> "Hello"

After you fork a Future, the computation will start running. If you wish to cancel the computation, you may use the function returned by fork:

var fut = Future.after(300, 'hello');
var cancel = fut.fork(console.error, console.log);
cancel();
//Nothing will happen. The Future was cancelled before it could settle.

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 (likely uncatchable) Error will be thrown.

Future.reject(new Error('It broke'))
.fold(S.Left, S.Right)
.value(console.log);
//> Left([Error: It broke])

Just like fork, value returns the Cancel function:

Future.after(300, 'hello').value(console.log)();
//Nothing will happen. The Future was cancelled before it could settle.

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.

Future.of('hello').done((err, val) => console.log(val));
//> "hello"

This is like fork, but instead of taking two unary functions, it takes a single binary function. As with fork(), done() returns Cancel:

var m = Future.after(300, 'hello');
var cancel = m.done((err, val) => console.log(val));
cancel();
//Nothing will happen. The Future was cancelled before it could settle.

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. 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, so I recommend against using it for anything else.

Future.of('Hello').promise().then(console.log);
//> "Hello"

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.

Future.after(100, 'hello')
.race(Future.after(50, 'bye'))
.fork(console.error, console.log);
//> "bye"

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);
//! "nope"

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. Basically like calling Future.parallel with exactly two Futures:

var a = Future.of('a');
var b = Future.of('b');

Future.both(a, b).fork(console.error, console.log);
//> ['a', 'b']

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.

var 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:

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);
//> [ Right(0), Left("failed"), Right(2), Right(3) ]

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 you 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');

//Some dummy values
var x = 1;
var f = a => a + 1;

//The following two are equal ways to construct a ConcurrentFuture
var parx = of(Par, x);
var parf = Par(of(Future, f));

//We can make use of parallel apply
seq(ap(parx, parf)).value(console.log);
//> 2

//Or concurrent sequencing
seq(sequence(Par, [parx, parf])).value(console.log);
//> [x, f]
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);
//> 1

seq(alt(Par(Future.after(20, 1)), Future.after(10, 2))).value(console.log);
//> 2

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);

In the case that a hooked Future is cancelled after the resource was acquired, dispose will be executed and immediately cancelled. This means that rejections which may happen during this disposal are silently ignored. To ensure that resources are disposed during cancellation, you might synchronously dispose resources in the cancel function of the disposal Future:

var closeConnection = conn => Future((rej, res) => {

  //We try to dispose gracefully.
  conn.flushGracefully(err => {
    if(err === null){
      conn.close();
      res();
    }else{
      rej(err);
    }
  });

  //On cancel, we force dispose.
  return () => conn.close();

});

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.

Future.of('Hello')
.finally(Future.of('All done!').map(console.log))
.fork(console.error, console.log);
//> "All done!"
//> "Hello"

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');
//> "All done!"
//> "Hello"

As with hook; when the Future is cancelled before the finally computation is running, the finally computation is executed and immediately cancelled.

This function has an alias lastly, for environments in which finally is a reserved word.

Utility functions

cache

cache :: Future a b -> Future a b
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.

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);
//> "Reading some big data"
//> "{...}"

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);
//> true

var m2 = Future2(noop);
Future1.isFuture(m2) === (m2 instanceof Future1);
//> false

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

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

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

About

🦋 FantasyLand compliant (monadic) alternative to Promises

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • JavaScript 99.9%
  • Shell 0.1%