Comparing version 1.0.3 to 1.1.0
@@ -104,2 +104,3 @@ //// ____ _ _ | ||
const fid = f => f.name ? f.name : '<anonymous>'; | ||
const ordinal = ['first', 'second', 'third', 'fourth', 'fifth']; | ||
@@ -142,3 +143,3 @@ //Partially apply a function with a single argument. | ||
throw new TypeError( | ||
`${it} expects argument ${at} to ${expected}\n Actual: ${show(actual)}` | ||
`${it} expects its ${ordinal[at]} argument to ${expected}\n Actual: ${show(actual)}` | ||
); | ||
@@ -215,2 +216,6 @@ } | ||
function check$swap(it){ | ||
if(!isFuture(it)) error$invalidContext('Future#swap', it); | ||
} | ||
function check$race(it, m){ | ||
@@ -232,2 +237,27 @@ if(!isFuture(it)) error$invalidContext('Future#race', it); | ||
function check$hook(it, f, g){ | ||
if(!isFuture(it)) error$invalidContext('Future#hook', it); | ||
if(!isFunction(f)) error$invalidArgument('Future#hook', 0, 'be a function', f); | ||
if(!isFunction(g)) error$invalidArgument('Future#hook', 1, 'be a function', g); | ||
} | ||
function check$hook$f(m, f, x){ | ||
if(!isFuture(m)) throw new TypeError( | ||
'Future#hook expects the first function its given to return a Future' | ||
+ `\n Actual: ${show(m)}\n From calling: ${showf(f)}\n With: ${show(x)}` | ||
); | ||
} | ||
function check$hook$g(m, g, x){ | ||
if(!isFuture(m)) throw new TypeError( | ||
'Future#hook expects the second function its given to return a Future' | ||
+ `\n Actual: ${show(m)}\n From calling: ${showf(g)}\n With: ${show(x)}` | ||
); | ||
} | ||
function check$finally(it, m){ | ||
if(!isFuture(it)) error$invalidContext('Future#finally', it); | ||
if(!isFuture(m)) error$invalidArgument('Future#finally', 0, 'be a Future', m); | ||
} | ||
function check$value(it, f){ | ||
@@ -416,2 +446,10 @@ if(!isFuture(it)) error$invalidContext('Future#value', it); | ||
function Future$swap(){ | ||
check$swap(this); | ||
const _this = this; | ||
return new FutureClass(function Future$swap$fork(rej, res){ | ||
_this._f(res, rej); | ||
}); | ||
} | ||
function Future$toString(){ | ||
@@ -456,2 +494,38 @@ return `Future(${this._f.toString()})`; | ||
function Future$hook(f, g){ | ||
check$hook(this, f, g); | ||
const _this = this; | ||
return new FutureClass(function Future$hook$fork(rej, res){ | ||
_this.fork(rej, function Future$hook$res(r){ | ||
const m = g(r); | ||
check$hook$g(m, g, r); | ||
m._f(e => { | ||
const c = f(r); | ||
check$hook$f(c, f, r); | ||
c._f(rej, _ => rej(e)) | ||
}, x => { | ||
const c = f(r); | ||
check$hook$f(c, f, r); | ||
c._f(rej, _ => res(x)) | ||
}) | ||
}); | ||
}); | ||
} | ||
function Future$finally(m){ | ||
check$finally(this, m); | ||
const _this = this; | ||
return new FutureClass(function Future$finally$fork(rej, res){ | ||
_this._f(function Future$finally$rej(e){ | ||
m._f(rej, function Future$finally$rej$res(){ | ||
rej(e); | ||
}); | ||
}, function Future$finally$res(x){ | ||
m._f(rej, function Future$finally$res$res(){ | ||
res(x); | ||
}); | ||
}); | ||
}); | ||
} | ||
function Future$value(f){ | ||
@@ -519,2 +593,3 @@ check$value(this, f); | ||
ap: Future$ap, | ||
swap: Future$swap, | ||
toString: Future$toString, | ||
@@ -525,2 +600,4 @@ inspect: Future$toString, | ||
fold: Future$fold, | ||
hook: Future$hook, | ||
finally: Future$finally, | ||
value: Future$value, | ||
@@ -586,3 +663,3 @@ promise: Future$promise, | ||
if(m && typeof m[method] === 'function') return m[method](a); | ||
error$invalidArgument(`Future.${method}`, 1, `have a "${method}" method`, m); | ||
error$invalidArgument(`Future.${method}`, 0, `have a "${method}" method`, m); | ||
}; | ||
@@ -607,2 +684,15 @@ f.toString = () => `function dispatch$${method}(m, a){ m.${method}(a) }`; | ||
//Creates a dispatcher for a binary method, but takes the object first rather than last. | ||
function createInvertedBinaryDispatcher(method){ | ||
const f = function invertedBinaryDispatch(m, a, b){ | ||
if(arguments.length === 1) return unaryPartial(f, m); | ||
if(arguments.length === 2) return binaryPartial(f, m, a); | ||
if(m && typeof m[method] === 'function') return m[method](a, b); | ||
error$invalidArgument(`Future.${method}`, 0, `have a "${method}" method`, m); | ||
}; | ||
f.toString = () => `function dispatch$${method}(m, a, b){ m.${method}(a, b) }`; | ||
f.inspect = () => `[Function: dispatch$${method}]`; | ||
return f; | ||
} | ||
Future.chain = createUnaryDispatcher('chain'); | ||
@@ -614,2 +704,3 @@ Future.chainRej = createUnaryDispatcher('chainRej'); | ||
Future.ap = createInvertedUnaryDispatcher('ap'); | ||
Future.swap = createNullaryDispatcher('swap'); | ||
Future.fork = createBinaryDispatcher('fork'); | ||
@@ -619,2 +710,4 @@ Future.race = createUnaryDispatcher('race'); | ||
Future.fold = createBinaryDispatcher('fold'); | ||
Future.hook = createInvertedBinaryDispatcher('hook'); | ||
Future.finally = createUnaryDispatcher('finally'); | ||
Future.value = createUnaryDispatcher('value'); | ||
@@ -621,0 +714,0 @@ Future.promise = createNullaryDispatcher('promise'); |
{ | ||
"name": "fluture", | ||
"version": "1.0.3", | ||
"version": "1.1.0", | ||
"description": "A mathematically correct alternative to Promises for asynchronous control flow", | ||
@@ -17,4 +17,4 @@ "main": "fluture.js", | ||
"test:opt": "node --allow-natives-syntax --trace-opt --trace-deopt --trace-inlining scripts/test-opt", | ||
"test:unit": "node --harmony-destructuring ./node_modules/.bin/_mocha --ui bdd --reporter spec --check-leaks --full-trace", | ||
"test:coverage": "node --harmony-destructuring node_modules/.bin/istanbul cover --report html ./node_modules/.bin/_mocha -- --ui bdd --reporter dot --bail --check-leaks && codecov" | ||
"test:unit": "node ./node_modules/.bin/_mocha --ui bdd --reporter spec --check-leaks --full-trace", | ||
"test:coverage": "node node_modules/.bin/istanbul cover --report html ./node_modules/.bin/_mocha -- --ui bdd --reporter dot --bail --check-leaks && codecov" | ||
}, | ||
@@ -59,3 +59,3 @@ "author": "Aldwin Vlasblom <aldwin.vlasblom@gmail.com> (https://github.com/Avaq)", | ||
"data.task": "^3.0.0", | ||
"eslint": "^2.2.0", | ||
"eslint": "^2.13.1", | ||
"istanbul": "^0.4.2", | ||
@@ -71,4 +71,4 @@ "jsverify": "^0.7.1", | ||
"rimraf": "^2.4.3", | ||
"sanctuary": "^0.10.0" | ||
"sanctuary": "^0.11.1" | ||
} | ||
} |
333
README.md
@@ -18,6 +18,19 @@ # Fluture | ||
## Usage | ||
```js | ||
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" | ||
``` | ||
## Table of contents | ||
- [Table of contents](#table-of-contents) | ||
- [Usage](#usage) | ||
- [Motivation and Features](#motivation-and-features) | ||
@@ -35,14 +48,19 @@ - [Documentation](#documentation) | ||
* [node](#node) | ||
1. [Consuming Futures](#consuming-futures) | ||
* [fork](#fork) | ||
* [value](#value) | ||
* [promise](#promise) | ||
1. [Transforming Futures](#transforming-futures) | ||
* [map](#map) | ||
* [mapRej](#maprej) | ||
* [bimap](#bimap) | ||
* [chain](#chain) | ||
* [ap](#ap) | ||
* [swap](#swap) | ||
1. [Error handling](#error-handling) | ||
* [mapRej](#maprej) | ||
* [chainRej](#chainrej) | ||
* [ap](#ap) | ||
* [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) | ||
@@ -55,2 +73,3 @@ * [race](#race) | ||
* [isForkable](#isforkable) | ||
* [cache](#cache) | ||
* [do](#do) | ||
@@ -61,15 +80,2 @@ 1. [Futurization](#futurization) | ||
## Usage | ||
```js | ||
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" | ||
``` | ||
## Motivation and Features | ||
@@ -91,5 +97,8 @@ | ||
[Hindley-Milner][9] type signatures are used to document functions. A list of | ||
all types used within these signatures follows: | ||
[Hindley-Milner][9] 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: | ||
- **Forkable** - Any Object with a `fork` method that takes at least two | ||
@@ -132,2 +141,3 @@ arguments. This includes instances of Fluture, instances of Task from | ||
#### of | ||
##### `#of :: a -> Future _ a` | ||
##### `.of :: a -> Future _ a` | ||
@@ -221,61 +231,2 @@ | ||
### Consuming Futures | ||
#### fork | ||
##### `#fork :: Future a b ~> (a -> Void), (b -> Void) -> Void` | ||
##### `.fork :: (a -> Void) -> (b -> Void) -> Future a b -> Void` | ||
Execute the Future by calling the `fork` function that was passed to it at | ||
[construction](#creation) with the `reject` and `resolve` callbacks. Futures are | ||
*lazy*, which means even if you've `map`ped or `chain`ed over them, they'll do | ||
*nothing* if you don't eventually fork them. | ||
```js | ||
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" | ||
``` | ||
#### value | ||
##### `#value :: Future a b ~> (b -> Void) -> Void` | ||
##### `.value :: (b -> Void) -> Future a b -> 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. | ||
```js | ||
Future.reject(new Error('It broke')) | ||
.fold(S.Left, S.Right) | ||
.value(console.log) | ||
//> Left([Error: It broke]) | ||
``` | ||
#### promise | ||
##### `#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, which means it's considered dangerous to use. | ||
```js | ||
Future.of('Hello').promise().then(console.log); | ||
//> "Hello" | ||
``` | ||
### Transforming Futures | ||
@@ -301,17 +252,2 @@ | ||
#### mapRej | ||
##### `#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. | ||
```js | ||
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!] | ||
``` | ||
#### bimap | ||
@@ -353,18 +289,2 @@ ##### `#bimap :: Future a b ~> (a -> c) -> (b -> d) -> Future c d` | ||
#### chainRej | ||
##### `#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. | ||
```js | ||
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 | ||
@@ -387,2 +307,53 @@ ##### `#ap :: Future a (b -> c) ~> Future a b -> Future a c` | ||
#### swap | ||
##### `#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. | ||
```js | ||
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" | ||
``` | ||
### 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 | ||
##### `#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. | ||
```js | ||
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 | ||
##### `#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. | ||
```js | ||
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 | ||
@@ -411,2 +382,119 @@ ##### `#fold :: Future a b ~> (a -> c), (b -> c) -> Future _ c` | ||
### 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` | ||
Much like [`chain`](#chain), but takes a "cleanup" computation first, which runs | ||
*after* the second settles (successfully or unsuccessfully). This allows for | ||
acquired resources to be disposed, connections to be closed, etc. | ||
```js | ||
const withConnection = Future.hook( | ||
openConnection('localhost'), | ||
closeConnection | ||
); | ||
withConnection( | ||
conn => query(conn, 'EAT * cakes FROM bakery') | ||
) | ||
.fork(console.error, console.log) | ||
``` | ||
Take care when using this in combination with [`cache`](#cache). Hooking relies | ||
on the first computation providing a fresh resource every time it's forked. | ||
#### finally | ||
##### `#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. | ||
```js | ||
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()`: | ||
```js | ||
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" | ||
``` | ||
### Consuming Futures | ||
#### fork | ||
##### `#fork :: Future a b ~> (a -> Void), (b -> Void) -> Void` | ||
##### `.fork :: (a -> Void) -> (b -> Void) -> Future a b -> Void` | ||
Execute the Future by calling the `fork` function that was passed to it at | ||
[construction](#creation) with the `reject` and `resolve` callbacks. Futures are | ||
*lazy*, which means even if you've `map`ped or `chain`ed over them, they'll do | ||
*nothing* if you don't eventually fork them. | ||
```js | ||
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" | ||
``` | ||
#### value | ||
##### `#value :: Future a b ~> (b -> Void) -> Void` | ||
##### `.value :: (b -> Void) -> Future a b -> 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. | ||
```js | ||
Future.reject(new Error('It broke')) | ||
.fold(S.Left, S.Right) | ||
.value(console.log) | ||
//> Left([Error: It broke]) | ||
``` | ||
#### promise | ||
##### `#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, which means it's considered dangerous to use. | ||
```js | ||
Future.of('Hello').promise().then(console.log); | ||
//> "Hello" | ||
``` | ||
### Parallelism | ||
@@ -541,2 +629,3 @@ | ||
#### cache | ||
##### `.cache :: Future a b -> Future a b` | ||
@@ -591,2 +680,20 @@ | ||
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`][7]: | ||
```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" | ||
``` | ||
### Futurization | ||
@@ -593,0 +700,0 @@ |
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
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
50953
719
740