Comparing version 5.0.0 to 6.0.0
{ | ||
"name": "fluture", | ||
"version": "5.0.0", | ||
"version": "6.0.0", | ||
"description": "FantasyLand compliant (monadic) alternative to Promises", | ||
"main": "fluture.js", | ||
"main": "index.js", | ||
"module": "index.es.js", | ||
"files": [ | ||
"es5.js" | ||
"src", | ||
"index.es.js" | ||
], | ||
"repository": "https://github.com/fluture-js/Fluture.git", | ||
"scripts": { | ||
"build": "babel fluture.js -o es5.js", | ||
"clean": "rimraf npm-debug.log coverage", | ||
"lint": "eslint fluture.js test", | ||
"build": "rollup -c", | ||
"clean": "rimraf npm-debug.log coverage index.js index.test.js", | ||
"lint": "eslint src test index.es.js README.md", | ||
"lint:fix": "npm run lint -- --fix", | ||
@@ -19,8 +21,7 @@ "lint:readme": "remark --no-stdout --frail -u remark-validate-links README.md", | ||
"toc": "node scripts/toc.js", | ||
"test": "npm run test:all && npm run test:coverage && codecov", | ||
"test": "npm run lint && npm run lint:readme && npm run test:unit", | ||
"test:opt": "node --allow-natives-syntax --trace-opt --trace-deopt --trace-inlining scripts/test-opt", | ||
"test:mem": "node scripts/test-mem", | ||
"test:all": "npm run lint && npm run lint:readme && npm run test:unit", | ||
"test:unit": "_mocha --ui bdd --reporter list --check-leaks --full-trace test/**.test.js", | ||
"test:coverage": "npm run clean && istanbul cover --report html _mocha -- --ui bdd --reporter dot --check-leaks --bail test/**.test.js" | ||
"test:unit": "rollup -c rollup.config.test.js && mocha --require source-map-support/register --reporter test/reporter.js index.test.js", | ||
"test:coverage": "codecov" | ||
}, | ||
@@ -56,30 +57,36 @@ "author": "Aldwin Vlasblom <aldwin.vlasblom@gmail.com> (https://github.com/Avaq)", | ||
"dependencies": { | ||
"concurrify": "^0.1.0", | ||
"concurrify": "^1.0.0", | ||
"inspect-f": "^1.2.0", | ||
"sanctuary-type-classes": "^3.0.0", | ||
"sanctuary-type-identifiers": "^1.0.0" | ||
"sanctuary-type-classes": "^5.1.0", | ||
"sanctuary-type-identifiers": "^2.0.0" | ||
}, | ||
"devDependencies": { | ||
"babel-cli": "^6.18.0", | ||
"babel-preset-latest": "^6.16.0", | ||
"benchmark": "^2.1.0", | ||
"chai": "^3.4.0", | ||
"codecov": "^1.0.1", | ||
"codecov": "^2.2.0", | ||
"data.task": "^3.0.0", | ||
"denque": "^1.1.1", | ||
"eslint": "^3.0.1", | ||
"eslint-config-warp": "^1.2.0", | ||
"eslint-plugin-markdown": "^1.0.0-beta.6", | ||
"fantasy-land": "^3.0.0", | ||
"fun-task": "^1.5.1", | ||
"istanbul": "^0.4.2", | ||
"jsverify": "^0.7.1", | ||
"lazy-either": "^1.0.3", | ||
"istanbul": "^0.4.5", | ||
"jsverify": "^0.8.2", | ||
"lodash.curry": "^4.1.1", | ||
"markdown-toc": "^1.0.2", | ||
"mocha": "^3.0.2", | ||
"ramda-fantasy": "^0.7.0", | ||
"remark-cli": "^2.1.0", | ||
"remark-validate-links": "^5.0.0", | ||
"ramda": "^0.23.0", | ||
"ramda-fantasy": "^0.8.0", | ||
"remark-cli": "^3.0.1", | ||
"remark-validate-links": "^6.0.0", | ||
"rimraf": "^2.4.3", | ||
"rollup": "^0.41.6", | ||
"rollup-plugin-buble": "^0.15.0", | ||
"rollup-plugin-istanbul": "^1.1.0", | ||
"rollup-plugin-multi-entry": "^2.0.1", | ||
"rollup-plugin-uglify": "^1.0.1", | ||
"source-map-support": "^0.4.14", | ||
"xyz": "^2.0.1" | ||
} | ||
} |
647
README.md
@@ -20,2 +20,3 @@ # [![Fluture](logo.png)](#butterfly) | ||
* [Resource management utilities](#resource-management). | ||
* [Stack safe composition and recursion](#stack-safety). | ||
* Great integration with functional libraries such as [Sanctuary][S]. | ||
@@ -34,9 +35,36 @@ * A pleasant debugging experience through informative error messages. | ||
### In ES5 or older environments | ||
Fluture depends on these functions being present: | ||
[`Object.create`][JS:Object.create], | ||
[`Object.assign`][JS:Object.assign] and [`Array.isArray`][JS:Array.isArray]. | ||
You may need to polyfill one or more. | ||
<!-- eslint-disable no-var --> | ||
```js | ||
const fs = require('fs'); | ||
const Future = require('fluture'); | ||
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" | ||
``` | ||
### In ES6 or newer environments | ||
The `package.json` sets a `module`-field for build-tools like [Rollup][]. | ||
```js | ||
import {readFile} from 'fs'; | ||
import {node, encase} from 'fluture'; | ||
const getPackageName = file => | ||
Future.node(done => fs.readFile(file, 'utf8', done)) | ||
.chain(Future.encase(JSON.parse)) | ||
node(done => { readFile(file, 'utf8', done) }) | ||
.chain(encase(JSON.parse)) | ||
.map(x => x.name); | ||
@@ -49,4 +77,2 @@ | ||
For front-end applications and node <v4, please use `require('fluture/es5')`. | ||
## Table of contents | ||
@@ -57,46 +83,47 @@ | ||
- [Documentation](#documentation) | ||
1. [Type signatures](#type-signatures) | ||
1. [Creating Futures](#creating-futures) | ||
* [Future](#future) | ||
* [of](#of) | ||
* [never](#never) | ||
* [reject](#reject) | ||
* [after](#after) | ||
* [rejectAfter](#rejectafter) | ||
* [try](#try) | ||
* [encase](#encase) | ||
* [fromPromise](#frompromise) | ||
* [node](#node) | ||
* [chainRec](#chainrec) | ||
1. [Transforming Futures](#transforming-futures) | ||
* [map](#map) | ||
* [bimap](#bimap) | ||
* [chain](#chain) | ||
* [ap](#ap) | ||
* [swap](#swap) | ||
1. [Error handling](#error-handling) | ||
* [mapRej](#maprej) | ||
* [chainRej](#chainrej) | ||
* [fold](#fold) | ||
1. [Resource management](#resource-management) | ||
* [hook](#hook) | ||
* [finally](#finally) | ||
1. [Consuming Futures](#consuming-futures) | ||
* [fork](#fork) | ||
* [value](#value) | ||
* [promise](#promise) | ||
1. [Parallelism](#parallelism) | ||
* [race](#race) | ||
* [and](#and) | ||
* [or](#or) | ||
* [both](#both) | ||
* [parallel](#parallel) | ||
* [ConcurrentFuture](#concurrentfuture) | ||
1. [Utility functions](#utility-functions) | ||
* [isFuture](#isfuture) | ||
* [cache](#cache) | ||
* [do](#do) | ||
1. [Sanctuary](#sanctuary) | ||
1. [Futurization](#futurization) | ||
- [Benchmarks](#benchmarks) | ||
1. [Type signatures](#type-signatures) | ||
1. [Stack safety](#stack-safety) | ||
1. [Creating Futures](#creating-futures) | ||
* [Future](#future) | ||
* [of](#of) | ||
* [reject](#reject) | ||
* [after](#after) | ||
* [rejectAfter](#rejectafter) | ||
* [cache](#cache) | ||
* [do](#do) | ||
* [try](#try) | ||
* [encase](#encase) | ||
* [encaseP](#encasep) | ||
* [node](#node) | ||
1. [Transforming Futures](#transforming-futures) | ||
* [map](#map) | ||
* [bimap](#bimap) | ||
* [chain](#chain) | ||
* [swap](#swap) | ||
* [mapRej](#maprej) | ||
* [chainRej](#chainrej) | ||
* [fold](#fold) | ||
1. [Combining Futures](#combining-futures) | ||
* [ap](#ap) | ||
* [and](#and) | ||
* [or](#or) | ||
1. [Resource management](#resource-management) | ||
* [hook](#hook) | ||
* [finally](#finally) | ||
1. [Consuming Futures](#consuming-futures) | ||
* [fork](#fork) | ||
* [value](#value) | ||
* [promise](#promise) | ||
1. [Parallelism](#parallelism) | ||
* [race](#race) | ||
* [both](#both) | ||
* [parallel](#parallel) | ||
* [ConcurrentFuture](#concurrentfuture) | ||
1. [Utility functions](#utility-functions) | ||
* [isFuture](#isfuture) | ||
* [never](#never) | ||
* [isNever](#isnever) | ||
1. [Sanctuary](#sanctuary) | ||
1. [Futurization](#futurization) | ||
1. [Casting Futures](#casting-futures) | ||
- [Butterfly](#butterfly) | ||
@@ -109,9 +136,8 @@ | ||
Fluture implements [FantasyLand 1][FL1], [FantasyLand 2][FL2], | ||
[FantasyLand 3][FL3] and [Static Land][6] compatible `Functor`, `Bifunctor`, | ||
`Apply`, `Applicative`, `Chain`, `ChainRec` and `Monad`. | ||
* `Future` implements [Fantasy Land][FL] and [Static Land][6] -compatible | ||
`Bifunctor`, `Monad` and `ChainRec` (`of`, `ap`, `map`, `bimap`, `chain`, `chainRec`). | ||
All versions of Fantasy Land are supported. | ||
* `Future.Par` implements [Fantasy Land 3][FL] `Alternative` (`of`, `zero`, `map`, `ap`, `alt`). | ||
* The Future representative contains a `@@type` property for [Sanctuary Type Identifiers][STI]. | ||
FantasyLand 0.x is *mostly* supported. Everything but `Apply` (`ap`) is, this | ||
means that dispatchers to FantasyLand 0.x `ap` will not work. | ||
## Documentation | ||
@@ -143,2 +169,28 @@ | ||
### 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`: | ||
```js | ||
const add1 = x => x + 1; | ||
let m = Future.of(1); | ||
for(let i = 0; i < 100000; i++){ | ||
m = m.map(add1); | ||
} | ||
m.fork(console.error, console.log); | ||
//> 100001 | ||
``` | ||
```js | ||
const m = (function recur(x){ | ||
const mx = Future.of(x + 1); | ||
return x < 100000 ? mx.chain(recur) : mx; | ||
}(1)); | ||
m.fork(console.error, console.log); | ||
//> 100001 | ||
``` | ||
### Creating Futures | ||
@@ -183,8 +235,2 @@ | ||
#### never | ||
##### `.never :: Future a a` | ||
A Future that never settles. Can be useful as an initial value when reducing | ||
with [`race`](#race), for example. | ||
#### reject | ||
@@ -218,4 +264,77 @@ ##### `.reject :: a -> Future a _` | ||
#### cache | ||
##### `.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. | ||
```js | ||
const {readFile} = require('fs'); | ||
const 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); | ||
//> "{...}" | ||
``` | ||
#### do | ||
##### `.do :: (() -> Iterator) -> Future a b` | ||
##### `.go :: (() -> Iterator) -> Future a b` | ||
A specialized version of [fantasy-do][4] 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](#type-signatures), 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. | ||
```js | ||
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`](#fold) | ||
the error into your control domain, I recommend folding into an [`Either`][S:Either]: | ||
```js | ||
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" | ||
``` | ||
This function has an alias `go`, for environments in which `do` is a reserved word. | ||
#### try | ||
##### `.try :: (() -> !a | b) -> Future a b` | ||
##### `.attempt :: (() -> !a | b) -> Future a b` | ||
@@ -228,8 +347,10 @@ Creates a Future which resolves with the result of calling the given function, | ||
```js | ||
const data = {foo: 'bar'} | ||
const data = {foo: 'bar'}; | ||
Future.try(() => data.foo.bar.baz) | ||
.fork(console.error, console.log) | ||
.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. | ||
#### encase | ||
@@ -245,5 +366,5 @@ ##### `.encase :: (a -> !e | r) -> a -> Future e r` | ||
```js | ||
const data = '{"foo" = "bar"}' | ||
const data = '{"foo" = "bar"}'; | ||
Future.encase(JSON.parse, data) | ||
.fork(console.error, console.log) | ||
.fork(console.error, console.log); | ||
//! [SyntaxError: Unexpected token =] | ||
@@ -257,5 +378,5 @@ ``` | ||
```js | ||
const data = '{"foo" = "bar"}' | ||
const safeJsonParse = Future.encase(JSON.parse) | ||
safeJsonParse(data).fork(console.error, console.log) | ||
const data = '{"foo" = "bar"}'; | ||
const safeJsonParse = Future.encase(JSON.parse); | ||
safeJsonParse(data).fork(console.error, console.log); | ||
//! [SyntaxError: Unexpected token =] | ||
@@ -267,18 +388,19 @@ ``` | ||
#### fromPromise | ||
##### `.fromPromise :: (a -> Promise e r) -> a -> Future e r` | ||
##### `.fromPromise2 :: (a, b -> Promise e r) -> a -> b -> Future e r` | ||
##### `.fromPromise3 :: (a, b, c -> Promise e r) -> a -> b -> c -> Future e r` | ||
#### encaseP | ||
##### `.tryP` :: (a -> Promise e r) -> 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 | ||
which calls the function to produce the Promise, and resolves with the Promise | ||
resolution value, or rejects with the Promise rejection reason. | ||
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. | ||
```js | ||
const fetchf = Future.fromPromise(fetch); | ||
const fetchf = Future.encaseP(fetch); | ||
fetchf('https://api.github.com/users/Avaq') | ||
.chain(res => Future.fromPromise(_ => res.json(), 0)) | ||
.chain(res => Future.tryP(_ => res.json())) | ||
.map(user => user.name) | ||
@@ -289,4 +411,4 @@ .fork(console.error, console.log); | ||
Furthermore; `fromPromise2` and `fromPromise3` are binary and ternary versions | ||
of `fromPromise`, applying two or three arguments to the given function respectively. | ||
Furthermore; `encaseP2` and `encaseP3` are binary and ternary versions | ||
of `encaseP`, applying two or three arguments to the given function respectively. | ||
@@ -304,27 +426,8 @@ #### node | ||
```js | ||
Future.node(done => fs.readFile('package.json', 'utf8', done)) | ||
.fork(console.error, console.log) | ||
const {readFile} = require('fs'); | ||
Future.node(done => readFile('package.json', 'utf8', done)) | ||
.fork(console.error, console.log); | ||
//> "{...}" | ||
``` | ||
#### chainRec | ||
##### `.chainRec :: ((b -> Next, c -> Done, b) -> Future a Iteration) -> b -> Future a c` | ||
Stack- and memory-safe asynchronous "recursion" based on [FantasyLand ChainRec][FL:chainrec]. | ||
Calls the given function with the initial value (as third argument), and expects | ||
a Future of an [Iteration](#type-signatures). 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. | ||
```js | ||
Future.chainRec((next, done, x) => Future.of(x < 1000000 ? next(x + 1) : done(x)), 0) | ||
.fork(console.error, console.log); | ||
//> 1000000 | ||
``` | ||
### Transforming Futures | ||
@@ -386,23 +489,2 @@ | ||
Note that, due to its lazy nature, the stack and/or heap will slowly fill up as | ||
you chain more over the same structure. It's therefore recommended that you use | ||
[`chainRec`](#chainrec) in cases where you wish to `chain` recursively or | ||
traverse a large list (10000+ items). | ||
#### ap | ||
##### `#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 or Apply to the value | ||
contained in the left-hand Future or Apply. If one of the Futures rejects the | ||
resulting Future will also be rejected. To learn more about the exact behaviour | ||
of `ap`, check out its [spec][FL:apply]. | ||
```js | ||
Future.of(1) | ||
.ap(Future.of(x => x + 1)) | ||
.fork(console.error, console.log); | ||
//> 2 | ||
``` | ||
#### swap | ||
@@ -422,8 +504,2 @@ ##### `#swap :: Future a b ~> Future b a` | ||
### Error handling | ||
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 | ||
@@ -437,6 +513,5 @@ ##### `#mapRej :: Future a b ~> (a -> c) -> Future c b` | ||
```js | ||
Future.reject(new Error('It broke!')).mapRej(err => { | ||
return new Error('Some extra info: ' + err.message); | ||
}) | ||
.fork(console.error, console.log) | ||
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!] | ||
@@ -455,5 +530,5 @@ ``` | ||
console.error(err); | ||
return Future.of('All is good') | ||
return Future.of('All is good'); | ||
}) | ||
.fork(console.error, console.log) | ||
.fork(console.error, console.log); | ||
//> "All is good" | ||
@@ -485,2 +560,72 @@ ``` | ||
### Combining Futures | ||
#### ap | ||
##### `#ap :: Future a (b -> c) ~> Future a b -> Future a c` | ||
##### `.ap :: Apply m => m (a -> b) -> m a -> m 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. | ||
```js | ||
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][FL:apply], | ||
the hidden `fantasy-land/ap`-method *does*. Therefore Future remains fully | ||
compliant to Fantasy Land. | ||
#### and | ||
##### `#and :: Future a b ~> Future a b -> Future a b` | ||
##### `.and :: Future a b -> Future a b -> Future a b` | ||
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. | ||
<!-- eslint-disable no-undef --> | ||
```js | ||
//An asynchronous version of: | ||
//isResolved() && getValue(); | ||
isResolved().and(getValue()); | ||
``` | ||
```js | ||
//Asynchronous "all", where the resulting Future will be the leftmost to reject: | ||
const 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` | ||
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. | ||
<!-- eslint-disable no-undef --> | ||
```js | ||
//An asynchronous version of: | ||
//planA() || planB(); | ||
planA().or(planB()); | ||
``` | ||
```js | ||
//Asynchronous "any", where the resulting Future will be the leftmost to resolve: | ||
const 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 | ||
``` | ||
### Resource management | ||
@@ -492,14 +637,13 @@ | ||
#### 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` | ||
Much like [`chain`](#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. | ||
Allows a Future-returning function to be decorated with resource acquistion | ||
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. | ||
<!-- eslint-disable no-undef --> | ||
```js | ||
@@ -514,3 +658,3 @@ const withConnection = Future.hook( | ||
) | ||
.fork(console.error, console.log) | ||
.fork(console.error, console.log); | ||
``` | ||
@@ -524,2 +668,3 @@ | ||
<!-- eslint-disable no-unused-vars --> | ||
```js | ||
@@ -546,3 +691,5 @@ const closeConnection = conn => Future((rej, res) => { | ||
##### `#finally :: Future a b ~> Future a c -> Future a b` | ||
##### `#lastly :: Future a b ~> Future a c -> Future a b` | ||
##### `.finally :: Future a c -> Future a b -> Future a b` | ||
##### `.lastly :: Future a c -> Future a b -> Future a b` | ||
@@ -556,3 +703,3 @@ Run a second Future after the first settles (successfully or unsuccessfully). | ||
.finally(Future.of('All done!').map(console.log)) | ||
.fork(console.error, console.log) | ||
.fork(console.error, console.log); | ||
//> "All done!" | ||
@@ -569,5 +716,5 @@ //> "Hello" | ||
Future.fork(console.error, console.log) | ||
]) | ||
]); | ||
program('Hello') | ||
program('Hello'); | ||
//> "All done!" | ||
@@ -581,2 +728,4 @@ //> "Hello" | ||
This function has an alias `lastly`, for environments in which `finally` is a reserved word. | ||
### Consuming Futures | ||
@@ -631,3 +780,3 @@ | ||
.fold(S.Left, S.Right) | ||
.value(console.log) | ||
.value(console.log); | ||
//> Left([Error: It broke]) | ||
@@ -669,3 +818,3 @@ ``` | ||
.race(Future.after(50, 'bye')) | ||
.fork(console.error, console.log) | ||
.fork(console.error, console.log); | ||
//> "bye" | ||
@@ -679,60 +828,6 @@ | ||
]) | ||
.fork(console.error, console.log) | ||
.fork(console.error, console.log); | ||
//! "nope" | ||
``` | ||
#### and | ||
##### `#and :: Future a b ~> Future a b -> Future a b` | ||
##### `.and :: Future a b -> Future a b -> Future a b` | ||
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 analogues to how JavaScript's and 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 (and possibly be cancelled) even | ||
if the first rejects. | ||
```js | ||
//An asynchronous version of: | ||
//const result = isResolved() && getValue(); | ||
const result = isResolved().and(getValue()); | ||
//Asynchronous "all", where the resulting Future will be the leftmost to reject: | ||
const 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` | ||
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. | ||
```js | ||
//An asynchronous version of: | ||
//const result = planA() || planB(); | ||
const result = planA().or(planB()); | ||
//Asynchronous "any", where the resulting Future will be the leftmost to resolve: | ||
const 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 | ||
``` | ||
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`. | ||
#### both | ||
@@ -746,7 +841,7 @@ ##### `#both :: Future a b ~> Future a b -> Future a b` | ||
```js | ||
Future.parallel(2, [a, b]) | ||
=== | ||
Future.both(a, b) | ||
=== | ||
a.both(b) | ||
const a = Future.of('a'); | ||
const b = Future.of('b'); | ||
Future.both(a, b).fork(console.error, console.log); | ||
//> ['a', 'b'] | ||
``` | ||
@@ -787,3 +882,3 @@ | ||
const stabalizedFutures = instableFutures.map(Future.fold(S.Left, S.Right)) | ||
const stabalizedFutures = instableFutures.map(Future.fold(S.Left, S.Right)); | ||
@@ -800,3 +895,3 @@ Future.parallel(Infinity, stabalizedFutures).fork(console.error, console.log); | ||
[`concurrify`][concurrify] to `Future`. It provides a mechanism for constructing | ||
a [FantasyLand `Alternative`][FL:alternative] from a member of `Future`. This | ||
a [Fantasy Land `Alternative`][FL:alternative] from a member of `Future`. This | ||
allows Futures to benefit from the Alternative Interface, which includes | ||
@@ -823,9 +918,12 @@ parallel `ap`, `zero` and `alt`. | ||
//We can make use of parallel apply | ||
seq(ap(parx, parf)).value(console.log) //> 2 | ||
seq(ap(parx, parf)).value(console.log); | ||
//> 2 | ||
//Or concurrent sequencing | ||
seq(sequence(Par, [parx, parf])).value(console.log) //> [x, f] | ||
seq(sequence(Par, [parx, parf])).value(console.log); | ||
//> [x, f] | ||
//Or racing with alternative | ||
seq(alt(zero(Par), parx)).value(console.log) //> 1 | ||
seq(alt(zero(Par), parx)).value(console.log); | ||
//> 1 | ||
``` | ||
@@ -839,86 +937,33 @@ | ||
Returns true for [Futures](#type-signatures) and false for everything else. This | ||
function (and [`S.is`][S:is]) also return `true` for instances of Future that were | ||
created within other contexts. It is therefore recommended to use this over | ||
function (and [`S.is`][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. | ||
<!-- eslint-disable no-unused-expressions --> | ||
```js | ||
const Future1 = require('/path/to/fluture'); | ||
const Future2 = require('/other/path/to/fluture'); | ||
const noop = () => {}; | ||
const m1 = Future1(noop); | ||
Future1.isFuture(m1) === (m1 instanceof Future1); | ||
//> true | ||
const m2 = Future2(noop); | ||
Future1.isFuture(m2) !== (m2 instanceof Future1); | ||
Future1.isFuture(m2) === (m2 instanceof Future1); | ||
//> false | ||
``` | ||
#### cache | ||
##### `.cache :: Future a b -> Future a b` | ||
#### never | ||
##### `.never :: Future a a` | ||
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. | ||
A Future that never settles. Can be useful as an initial value when reducing | ||
with [`race`](#race), for example. | ||
```js | ||
const eventualPackage = Future.cache( | ||
Future.node(done => { | ||
console.log('Reading some big data'); | ||
fs.readFile('package.json', 'utf8', done) | ||
}) | ||
); | ||
#### isNever | ||
##### `.isNever :: a -> Boolean` | ||
eventualPackage.fork(console.error, console.log); | ||
//> "Reading some big data" | ||
//> "{...}" | ||
Returns `true` if the given input is a `never`. | ||
eventualPackage.fork(console.error, console.log); | ||
//> "{...}" | ||
``` | ||
#### do | ||
##### `.do :: (() -> Iterator) -> Future a b` | ||
A specialized version of [fantasy-do][4] 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](#type-signatures), 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. | ||
```js | ||
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`](#fold) | ||
the error into your control domain, I recommend folding into an [`Either`][S:Either]: | ||
```js | ||
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" | ||
``` | ||
### Sanctuary | ||
@@ -979,7 +1024,21 @@ | ||
## Benchmarks | ||
### Casting Futures | ||
Simply run `node ./bench/<file>` to see how a specific method compares to | ||
implementations in `data.task` and `ramda-fantasy.Future`. | ||
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`](#isfuture) returns `false`, a conversion is necessary. Usually | ||
the most concise way of doing this is as follows: | ||
```js | ||
const NoFuture = require('incompatible-future'); | ||
const incompatible = NoFuture.of('Hello'); | ||
//Cast the incompatible Future to our version of Future: | ||
const compatible = Future(incompatible.fork.bind(incompatible)); | ||
compatible.both(Future.of('world')).value(console.log); | ||
//> ["Hello", "world"] | ||
``` | ||
## Butterfly | ||
@@ -991,6 +1050,7 @@ | ||
Credits for the logo go to [Erik Fuente][8]. | ||
---- | ||
Thanks to [Erik Fuente][8] for the logo, and [WEAREREASONABLEPEOPLE][9] for | ||
sponsoring the project. | ||
[MIT licensed](LICENSE) | ||
@@ -1004,5 +1064,2 @@ | ||
[FL]: https://github.com/fantasyland/fantasy-land | ||
[FL1]: https://github.com/fantasyland/fantasy-land/tree/v1.0.1 | ||
[FL2]: https://github.com/fantasyland/fantasy-land/tree/v2.2.0 | ||
[FL3]: https://github.com/fantasyland/fantasy-land | ||
[FL:alternative]: https://github.com/fantasyland/fantasy-land#alternative | ||
@@ -1014,4 +1071,7 @@ [FL:functor]: https://github.com/fantasyland/fantasy-land#functor | ||
[FL:bifunctor]: https://github.com/fantasyland/fantasy-land#bifunctor | ||
[FL:chainrec]: https://github.com/fantasyland/fantasy-land#chainrec | ||
[JS:Object.create]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign | ||
[JS:Object.assign]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create | ||
[JS:Array.isArray]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray | ||
[S]: https://sanctuary.js.org/ | ||
@@ -1022,2 +1082,4 @@ [S:Either]: https://sanctuary.js.org/#either-type | ||
[STI]: https://github.com/sanctuary-js/sanctuary-type-identifiers | ||
[Z:Functor]: https://github.com/sanctuary-js/sanctuary-type-classes#Functor | ||
@@ -1033,2 +1095,4 @@ [Z:Bifunctor]: https://github.com/sanctuary-js/sanctuary-type-classes#Bifunctor | ||
[Rollup]: https://rollupjs.org/ | ||
[1]: https://github.com/futurize/futurize | ||
@@ -1042,1 +1106,2 @@ [2]: https://drboolean.gitbooks.io/mostly-adequate-guide/content/ch7.html | ||
[8]: http://erikfuente.com/ | ||
[9]: http://wearereasonablepeople.nl/ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
45
1067
138755
27
1750
1
1
1
+ Addedconcurrify@1.1.1(transitive)
+ Addedsanctuary-show@1.0.0(transitive)
+ Addedsanctuary-type-classes@5.2.0(transitive)
+ Addedsanctuary-type-identifiers@2.0.1(transitive)
- Removedconcurrify@0.1.2(transitive)
- Removedsanctuary-type-classes@3.1.04.0.0(transitive)
Updatedconcurrify@^1.0.0