Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

fluture

Package Overview
Dependencies
Maintainers
1
Versions
109
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

fluture

A mathematically correct alternative to Promises for asynchronous control flow

  • 0.6.4
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
28K
decreased by-13.82%
Maintainers
1
Weekly downloads
 
Created
Source

Fluture

Fantasy Land

NPM Version Dependencies Build Status Code Coverage

Futures are containers which represent some eventual value as a result of an asynchronous computation, much like Promises. Unlike Promises, however, Futures are lazy and logical by design. They have a predictable API governed by the Fantasy Land algebraic JavaScript specification.

npm install --save fluture Requires a node 5.0.0 compatible environment like modern browsers, transpilers or Node 5+

Table of contents

Usage

Using the low level, high performance method API:

const Future = require('fluture');
const program = file =>
  Future.node(done => fs.readFile(file, 'utf8', done))
  .chain(Future.encase(JSON.parse))
  .map(x => x.name)
  .fork(console.error, console.log);
program('package.json');
//> "fluture"

Or use the high level, fully curried, functional dispatch API for function composition using composers like S.pipe:

const {node, chain, encase, map, fork} = require('fluture');
const program = S.pipe([
  file => node(fs.readFile.bind(fs, file, 'utf8')),
  chain(encase(JSON.parse)),
  map(x => x.name),
  fork(console.error, console.log)
]);
program('package.json')
//> "fluture"

Motivation and Features

Existing implementations of Future are a pain to debug. This library was made in an effort to provide great error messages when something goes wrong. Other features include:

To learn more about the differences between Fluture and other Future implementations, take a look at this wiki page.

Documentation

Type signatures

Hindley-Milner type signatures are used to document functions. A list of all types used within these signatures follows:

Creation

Future :: ((a -> Void), (b -> Void) -> Void) -> Future a b

The Future constructor. Creates a new instance of Future by taking a single parameter fork: A function which takes two callbacks. Both are continuations for an asynchronous computation. The first is reject, commonly abreviated to rej. The second resolve, which abreviates to res. The fork function is expected to call rej once an error occurs, or res with the result of the asynchronous computation.

const eventualThing = Future((rej, res) => {
  setTimeout(res, 500, 'world');
});

eventualThing.fork(
  console.error,
  thing => console.log(`Hello ${thing}!`)
);
//> "Hello world!"
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 :: (Void -> !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

Creates a Future which resolves with the result of calling the given function with the given value, or rejects with the error thrown by the function.

const data = '{"foo" = "bar"}';
const parseJson = Future.encase(JSON.parse);
parseJson('a').fork(console.error, console.log)
//> [SyntaxError: Unexpected token =]
encase2 :: (a, b -> !e | r) -> a -> b -> Future e r

Binary version of Future.encase.

encase3 :: (a, b, c -> !e | r) -> a -> b -> c -> Future e r

Ternary version of Future.encase.

node :: ((a, b -> Void) -> Void) -> 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)
//> "{...}"
parallel :: PositiveInteger -> [Future a b] -> Future a [b]

Creates a Future which when forked runs all Futures in the given array in parallel, ensuring no more than limit Futures are running at once.

const tenFutures = Array.from(Array(10).keys()).map(Future.after(20));

//Runs all Futures in sequence:
Future.parallel(1, tenFutures).fork(console.error, console.log);
//after about 200ms:
//> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

//Runs upto five Futures in parallel:
Future.parallel(5, tenFutures).fork(console.error, console.log);
//after about 40ms:
//> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

//Runs all Futures in parallel:
Future.parallel(Infinity, tenFutures).fork(console.error, console.log);
//after about 20ms:
//> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

If you want to settle all Futures, even if some may fail, you can use this in combination with fold:

const fourInstableFutures = Array.from(Array(4).keys()).map(
  i => Future(
    (rej, res) => setTimeout(
      () => Math.random() > 0.8 ? rej('failed') : res(i),
      20
    )
  )
);

const stabalizedFutures = fourInstableFutures.map(Future.fold(S.Left, S.Right))

Future.parallel(2, stabalizedFutures).fork(console.error, console.log);
//after about 40ms:
//> [ Right(0), Left("failed"), Right(2), Right(3) ]

Method API

fork :: Future a b ~> (a -> Void), (b -> Void) -> Void

Execute the Future by calling the fork function that was passed to it at construction with the reject and resolve callbacks. Futures are lazy, which means even if you've mapped or chained over them, they'll do nothing if you don't eventually fork them.

Future.of('world').fork(
  err => console.log(`Oh no! ${err.message}`),
  thing => console.log(`Hello ${thing}!`)
);
//> "Hello world!"

Future.reject(new Error('It broke!')).fork(
  err => console.log(`Oh no! ${err.message}`),
  thing => console.log(`Hello ${thing}!`)
);
//> "Oh no! It broke!"
map :: Future a b ~> (b -> c) -> Future a c

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

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, so is 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
chainRej :: Future a b ~> (a -> Future a c) -> 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"
ap :: Future a (b -> c) ~> Future a b -> Future a c

Apply the resolution value, which is expected to be a function (as in Future.of(a_function)), to the resolution value in the given 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(x => x + 1)
.ap(Future.of(1))
.fork(console.error, console.log);
//> 2
race :: Future a b ~> Future a b -> Future a b

Race two Futures against eachother. Creates a new Future which resolves or rejects with the resolution or rejection value of the first Future to settle.

Future.after(100, 'hello')
.race(Future.after(50, 'bye'))
.fork(console.error, console.log)
//> "bye"
or :: Future a b ~> Future a b -> Future a b

Logical or for Futures.

Returns a new Future which either resolves with the first resolutation value, or rejects with the last rejection value once and if both Futures reject.

This behaves analogues to how JavaScript's or operator does, except both Futures run simultaneously, so it is not short-circuited. That means that if the second has side-effects, they will run even if the first resolves.

//An asynchronous version of:
//const result = planA() || planB();

const result = planA().or(planB());

In the example, assume both plans return Futures. Both plans are executed in parallel. If planA resolves, the returned Future will resolve with its value. If planA fails there is always planB. If both plans fail then the returned Future will also reject using the rejection reason of planB.

fold :: Future a b ~> (a -> c), (b -> c) -> Future _ c

Applies the left function to the rejection value, or the right function to the resolution value, depending on which is present, and resolves with the result.

This provides a convenient means to ensure a Future is always resolved. It can be used with other type constructors, like S.Either, to maintain a representataion of failures:

Future.of('hello')
.fold(S.Left, S.Right)
.value(console.log);
//> Right('hello')

Future.reject('it broke')
.fold(S.Left, S.Right)
.value(console.log);
//> Left('it broke')
cache :: Future a b -> Future a b

Returns a 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);
//> "{...}"
value :: Future a b ~> (b -> Void) -> Void

Extracts the value from a resolved Future by forking it. Only use this function if you are sure the Future is going to be resolved, for example; after using .fold(). If the Future rejects and value was used, an (likely uncatchable) Error will be thrown.

Future.reject(new Error('It broke'))
.fold(S.Left, S.Right)
.value(console.log)
//> Left([Error: It broke])
promise :: Future a b ~> Promise b a

An alternative way to fork the Future. This eagerly forks the Future and returns a Promise of the result. This is useful if some API wants you to give it a Promise. It's the only method which forks the Future without a forced way to handle the rejection branch, which means it's considered dangerous to use.

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

Dispatcher API

fork :: (a -> Void) -> (b -> Void) -> Future a b -> Void

Dispatches the first and second arguments to the fork method of the third argument.

const consoleFork = fork(console.error, console.log);
consoleFork(of('Hello'));
//> "Hello"
map :: Functor m => (a -> b) -> m a -> m b

Dispatches the first argument to the map method of the second argument. Has the same effect as R.map.

chain :: Chain m => (a -> m b) -> m a -> m b

Dispatches the first argument to the chain method of the second argument. Has the same effect as R.chain.

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

Dispatches the first argument to the chainRej method of the second argument.

ap :: Apply m => m (a -> b) -> m a -> m b

Dispatches the second argument to the ap method of the first argument. Has the same effect as R.ap.

race :: Future a b -> Future a b -> Future a b

Dispatches the first argument to the race method of the second argument.

const first = futures => futures.reduce(race);
first([
  after(100, 'hello'),
  after(50, 'bye'),
  Future(rej => setTimeout(rej, 25, 'nope'))
])
.fork(console.error, console.log)
//> [Error nope]
or :: Future a b -> Future a b -> Future a b

Dispatches the first argument to the or method of the second argument.

const program = S.pipe([
  reject,
  or(of('second chance')),
  value(console.log)
]);

program('first chance')
> "second chance"
fold :: (a -> c) -> (b -> c) -> Future a b -> Future _ c

Dispatches the first and second arguments to the fold method of the third argument.

value :: (b -> Void) -> Future a b -> Void

Dispatches the first argument to the value method of the second argument.

promise :: Future a b -> Promise b a

Dispatches to the promise method.

Future.promise(Future.after(300, 'Hello')).then(console.log);
//> "Hello"

Utility functions

isFuture :: a -> Boolean

Returns true for Futures and false for everything else.

isForkable :: a -> Boolean

Returns true for Forkables and false for everything else.

Futurization

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"

Benchmarks

Simply run node ./bench/<file> to see how a specific method compares to implementations in data.task, ramda-fantasy.Future and Promise*.

* Promise is not included in all benchmarks because it tends to make the process run out of memory.

The name

A conjunction of the acronym to Fantasy Land (FL) and Future. Also "fluture" means butterfly in Romanian; A creature you might expect to see in Fantasy Land.


MIT licensed

Keywords

FAQs

Package last updated on 05 Apr 2016

Did you know?

Socket

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.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc