Security News
RubyGems.org Adds New Maintainer Role
RubyGems.org has added a new "maintainer" role that allows for publishing new versions of gems. This new permission type is aimed at improving security for gem owners and the service overall.
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 monadic by design. They conform to the Fantasy Land algebraic JavaScript specification.
Fluture boasts the following features:
For more information:
npm install --save fluture
Requires a node 4.0.0 compatible environment
const fs = require('fs');
const Future = require('fluture');
const getPackageName = file =>
Future.node(done => fs.readFile(file, 'utf8', done))
.chain(Future.encase(JSON.parse))
.map(x => x.name);
getPackageName('package.json')
.fork(console.error, console.log);
//> "fluture"
Fluture implements FantasyLand 1.x and Static Land compatible
Functor
, Bifunctor
, Apply
, Applicative
, Chain
, ChainRec
and Monad
.
Hindley-Milner type signatures are used to document functions. Signatures
starting with a .
refer to "static" functions, whereas signatures starting
with a #
refer to functions on the prototype.
A list of all types used within the 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
.next
-methods which conform to the Iterator protocol.{done, value}
-Objects as defined by the Iterator protocol.{done: false}
) Iteration.{done: true}
) Iteration.Future :: ((a -> ()), (b -> ()) -> 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:
const 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 :: a -> Future _ a
.of :: a -> Future _ a
Creates a Future which immediately resolves with the given value. This function is compliant with the Fantasy Land Applicative specification and is also available on the prototype.
const eventualThing = Future.of('world');
eventualThing.fork(
console.error,
thing => console.log(`Hello ${thing}!`)
);
//> "Hello world!"
.reject :: a -> Future a _
Creates a Future which immediately rejects with the given value. Just like of
but for the rejection branch.
.after :: Number -> b -> Future a b
Creates a Future which resolves with 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"
.try :: (() -> !a | b) -> Future a b
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]
.encase :: (a -> !e | r) -> a -> Future e r
.encase2 :: (a, b -> !e | r) -> a -> b -> Future e r
.encase3 :: (a, b, c -> !e | 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:
const 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):
const data = '{"foo" = "bar"}'
const 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.
.node :: ((a, b -> ()) -> ()) -> Future a b
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. To permanently turn a function into one that returns a Future, check out futurization.
Future.node(done => fs.readFile('package.json', 'utf8', done))
.fork(console.error, console.log)
//> "{...}"
.chainRec :: ((b -> Next, c -> Done, b) -> Future a Iteration) -> b -> Future a c
Stack- and memory-safe asynchronous "recursion" based on FantasyLand ChainRec.
Calls the given function with the initial value (as third argument), and expects
a Future of an Iteration. If the Iteration is incomplete
({done: false}
), then the function is called again with the Iteration value
until it returns a Future of a complete ({done: true}
) Iteration.
For convenience and interoperability, the first two arguments passed to the function are functions for creating an incomplete Iteration, and for creating a complete Iteration, respectively.
Future.chainRec((next, done, x) => Future.of(x < 1000000 ? next(x + 1) : done(x)), 0)
.fork(console.error, console.log);
//> 1000000
#map :: Future a b ~> (b -> c) -> Future a c
.map :: Functor m => (a -> b) -> m a -> m 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 :: Future a b ~> (a -> c) -> (b -> d) -> Future c d
.bimap :: Bifunctor m => (a -> b) -> (c -> d) -> m a c -> m b 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 :: Future a b ~> (b -> Future a c) -> Future a c
.chain :: Chain m => (a -> m b) -> m a -> m 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
#recur :: Future a b ~> (b -> Future a c) -> Future a c
.recur :: (b -> Future a c) -> Future a b -> Future a c
An alternative version of chain
which does not build up the cancel function.
This is useful in the case of a never-resolving recursive computation, in
order to prevent stack-overflow or out-of-memory errors:
const recursive = () => Future.after(200, 'world').recur(recursive);
const cancel = recursive();
process.on('SIGINT', cancel);
#ap :: Future a b ~> Future a (b -> c) -> Future a c
.ap :: Apply m => m (a -> b) -> m a -> m b
Applies the function contained in the right-hand Future to the value contained
in the left-hand Future. Both Futures involved will run in parallel, and if one
rejects the resulting Future will also be rejected. To learn more about the
exact behaviour of ap
, check out its spec.
Future.of(1)
.ap(Future.of(x => x + 1))
.fork(console.error, console.log);
//> 2
#swap :: Future a b ~> Future b a
.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"
Functions listed under this category allow you to get at or transform the rejection reason in Futures, or even coerce Futures back into the resolution branch in several different ways.
#mapRej :: Future a b ~> (a -> c) -> Future c b
.mapRej :: (a -> b) -> Future a c -> Future b c
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 => {
return new Error('Some extra info: ' + err.message);
})
.fork(console.error, console.log)
//! [Some extra info: It broke!]
#chainRej :: Future a b ~> (a -> Future a c) -> Future a c
.chainRej :: (a -> Future a c) -> Future a b -> Future a c
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 :: Future a b ~> (a -> c), (b -> c) -> Future _ c
.fold :: (a -> c) -> (b -> c) -> Future a b -> 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')
Functions listed under this category allow for more fine-grained control over the flow of acquired values.
#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
Much like chain
, but takes a "dispose" operation first, which runs
after the second settles (successfully or unsuccessfully). So 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.
const withConnection = Future.hook(
openConnection('localhost'),
closeConnection
);
withConnection(
conn => query(conn, 'EAT * cakes FROM bakery')
)
.fork(console.error, console.log)
Be careful when cancelling a hooked Future. If the resource was acquired but not
yet consumed, it will no longer be disposed. One way to work around this is to
have the consume
computation return a cancel function which forcefully
disposes of the resource.
Take care when using this in combination with cache
. Hooking relies
on the first operation providing a fresh resource every time it's forked.
#finally :: Future a b ~> Future a c -> Future a b
.finally :: Future a c -> Future a b -> 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()
:
const 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"
#fork :: Future a b ~> (a -> ()), (b -> ()) -> Cancel
.fork :: (a -> ()) -> (b -> ()) -> Future a b -> 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!"
const 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
:
const fut = Future.after(300, 'hello');
const cancel = fut.fork(console.error, console.log);
cancel();
//Nothing will happen. The Future was cancelled before it could settle.
#value :: Future a b ~> (b -> ()) -> Cancel
.value :: (b -> ()) -> Future a b -> 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.
#promise :: Future a b ~> Promise b a
.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"
#race :: Future a b ~> Future a b -> Future a b
.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"
const first = futures => futures.reduce(race);
first([
after(100, 'hello'),
after(50, 'bye'),
Future(rej => setTimeout(rej, 25, 'nope'))
])
.fork(console.error, console.log)
//! "nope"
#or :: Future a b ~> Future a b -> Future a b
.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 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());
const program = S.pipe([
reject,
or(of('second chance')),
value(console.log)
]);
program('first chance')
> "second chance"
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
.
.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.
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) ]
.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.
const Future1 = require('/path/to/fluture');
const Future2 = require('/other/path/to/fluture');
const m1 = Future1(noop);
Future1.isFuture(m1) === (m1 instanceof Future1);
const m2 = Future2(noop);
Future1.isFuture(m2) !== (m2 instanceof Future1);
.isForkable :: a -> Boolean
Returns true for Forkables and false for everything else.
.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);
//> "Reading some big data"
//> "{...}"
eventualPackage.fork(console.error, console.log);
//> "{...}"
.do :: (() -> 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*(){
const thing = yield Future.after(300, 'world');
const 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
:
const attempt = Future.fold(S.Left, S.Right);
const ajaxGet = url => Future.reject('Failed to load ' + url);
Future.do(function*(){
const 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"
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,347 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
RubyGems.org has added a new "maintainer" role that allows for publishing new versions of gems. This new permission type is aimed at improving security for gem owners and the service overall.
Security News
Node.js will be enforcing stricter semver-major PR policies a month before major releases to enhance stability and ensure reliable release candidates.
Security News
Research
Socket's threat research team has detected five malicious npm packages targeting Roblox developers, deploying malware to steal credentials and personal data.