Comparing version 0.4.1 to 0.5.0
308
fluture.js
@@ -25,2 +25,6 @@ /*global define*/ | ||
//////////////////// | ||
// Error handling // | ||
//////////////////// | ||
//A toString function to provide a slightly more meaningful representation of values. | ||
@@ -38,36 +42,31 @@ const toString = x => | ||
function error(message, actual){ | ||
return message + '\n Actual: ' + actual; | ||
function error$invalidArgument(it, at, expected, actual){ | ||
throw new TypeError( | ||
`${it} expects argument ${at} to ${expected}\n Actual: ${toString(actual)}` | ||
); | ||
} | ||
function error$invalidContext(it, actual){ | ||
throw new TypeError( | ||
`${it} was invoked outside the context of a Future. You might want to use` | ||
+ ` a dispatcher instead\n Called on: ${toString(actual)}` | ||
) | ||
} | ||
//Check input to Future. | ||
function check$Future(fork){ | ||
if(typeof fork !== 'function') throw new TypeError(error( | ||
'Future expects its argument to be a function', | ||
toString(fork) | ||
)); | ||
if(typeof fork !== 'function') error$invalidArgument('Future', 0, 'be a function', fork); | ||
} | ||
//Check input to Future#fork. | ||
function check$fork$rej(f){ | ||
if(typeof f !== 'function') throw new TypeError(error( | ||
'Future#fork expects its first argument to be a function', | ||
toString(f) | ||
)); | ||
function check$fork(it, rej, res){ | ||
if(!(it instanceof FutureClass)) error$invalidContext('Future#chain', it); | ||
if(typeof rej !== 'function') error$invalidArgument('Future#fork', 0, 'be a function', rej); | ||
if(typeof res !== 'function') error$invalidArgument('Future#fork', 1, 'be a function', res); | ||
} | ||
//Check input to Future#fork. | ||
function check$fork$res(f){ | ||
if(typeof f !== 'function') throw new TypeError(error( | ||
'Future#fork expects its second argument to be a function', | ||
toString(f) | ||
)); | ||
} | ||
//Check input to Future#chain. | ||
function check$chain(f){ | ||
if(typeof f !== 'function') throw new TypeError(error( | ||
'Future#chain expects its argument to be a function', | ||
toString(f) | ||
)); | ||
function check$chain(it, f){ | ||
if(!(it instanceof FutureClass)) error$invalidContext('Future#chain', it); | ||
if(typeof f !== 'function') error$invalidArgument('Future#chain', 0, 'be a function', f); | ||
} | ||
@@ -77,22 +76,30 @@ | ||
function check$chain$f(m, f, x){ | ||
if(!(m instanceof FutureClass)) throw new TypeError(error( | ||
'Future#chain expects the function its given to return a Future', | ||
`${toString(m)}\n From calling: ${toString(f)}\n With: ${toString(x)}` | ||
)); | ||
if(!(m instanceof FutureClass)) throw new TypeError( | ||
'Future#chain expects the function its given to return a Future' | ||
+ `\n Actual: ${toString(m)}\n From calling: ${toString(f)}\n With: ${toString(x)}` | ||
); | ||
} | ||
function check$chainRej(it, f){ | ||
if(!(it instanceof FutureClass)) error$invalidContext('Future.chainRej', it); | ||
if(typeof f !== 'function') error$invalidArgument('Future.chainRej', 0, 'a function', f); | ||
} | ||
function check$chainRej$f(m, f, x){ | ||
if(!(m instanceof FutureClass)) throw new TypeError( | ||
'Future.chainRej expects the function its given to return a Future' | ||
+ `\n Actual: ${toString(m)}\n From calling: ${toString(f)}\n With: ${toString(x)}` | ||
); | ||
} | ||
//Check input to Future#map. | ||
function check$map(f){ | ||
if(typeof f !== 'function') throw new TypeError(error( | ||
'Future#map expects its argument to be a function', | ||
toString(f) | ||
)); | ||
function check$map(it, f){ | ||
if(!(it instanceof FutureClass)) error$invalidContext('Future#map', it); | ||
if(typeof f !== 'function') error$invalidArgument('Future#map', 0, 'be a function', f); | ||
} | ||
//Check input to Future#ap. | ||
function check$ap(m){ | ||
if(!(m instanceof FutureClass)) throw new TypeError(error( | ||
'Future#ap expects its argument to be a Future', | ||
toString(m) | ||
)); | ||
function check$ap(it, m){ | ||
if(!(it instanceof FutureClass)) error$invalidContext('Future#map', it); | ||
if(!(m instanceof FutureClass)) error$invalidArgument('Future#ap', 0, 'be a Future', m); | ||
} | ||
@@ -102,13 +109,14 @@ | ||
function check$ap$f(f){ | ||
if(typeof f !== 'function') throw new TypeError(error( | ||
'Future#ap was called on something other than Future<Function>', | ||
`Future.of(${toString(f)})` | ||
)); | ||
if(typeof f !== 'function') throw new TypeError( | ||
`Future#ap can only b used on Future<Function> but was used on: ${toString(f)}` | ||
); | ||
} | ||
function check$race(it, m){ | ||
if(!(it instanceof FutureClass)) error$invalidContext('Future.race', it); | ||
if(!(m instanceof FutureClass)) error$invalidArgument('Future.race', 0, 'be a function', m); | ||
} | ||
function check$cache(m){ | ||
if(!(m instanceof FutureClass)) throw new TypeError(error( | ||
'Future.cache expects its argument to be a Future', | ||
toString(m) | ||
)); | ||
if(!(m instanceof FutureClass)) error$invalidArgument('Future.cache', 0, 'be a Future', m); | ||
} | ||
@@ -126,58 +134,18 @@ | ||
function check$liftNode(f){ | ||
if(typeof f !== 'function') throw new TypeError(error( | ||
'Future.liftNode expects its first argument to be a function', | ||
toString(f) | ||
)); | ||
} | ||
function check$liftPromise(f){ | ||
if(typeof f !== 'function') throw new TypeError(error( | ||
'Future.liftPromise expects its first argument to be a function', | ||
toString(f) | ||
)); | ||
} | ||
function check$liftPromise$f(m, f, x){ | ||
if(!m || typeof m.then !== 'function') throw new TypeError(error( | ||
'Future.liftPromise expects the function its given to return a Promise', | ||
`${toString(m)}\n From calling: ${toString(f)}\n With: ${toString(x)}` | ||
)); | ||
} | ||
function check$after(n){ | ||
if(typeof n !== 'number') throw new TypeError(error( | ||
'Future.after expects its first argument to be a number', | ||
toString(n) | ||
)); | ||
if(typeof n !== 'number') error$invalidArgument('Future.after', 0, 'be a number', n); | ||
} | ||
function check$try(f){ | ||
if(typeof f !== 'function') throw new TypeError(error( | ||
'Future.try expects its first argument to be a function', | ||
toString(f) | ||
)); | ||
if(typeof f !== 'function') error$invalidArgument('Future.try', 0, 'be a function', f); | ||
} | ||
function check$node(f){ | ||
if(typeof f !== 'function') throw new TypeError(error( | ||
'Future.node expects its first argument to be a function', | ||
toString(f) | ||
)); | ||
if(typeof f !== 'function') error$invalidArgument('Future.node', 0, 'be a function', f); | ||
} | ||
function check$race$m1(m){ | ||
if(!(m instanceof FutureClass)) throw new TypeError(error( | ||
'Future.race expects its first argument to be a Future', | ||
toString(m) | ||
)); | ||
} | ||
//////////// | ||
// Future // | ||
//////////// | ||
function check$race$m2(m){ | ||
if(!(m instanceof FutureClass)) throw new TypeError(error( | ||
'Future.race expects its second argument to be a Future', | ||
toString(m) | ||
)); | ||
} | ||
//Constructor. | ||
@@ -202,4 +170,3 @@ function FutureClass(f){ | ||
function Future$fork(rej, res){ | ||
check$fork$rej(rej); | ||
check$fork$res(res); | ||
check$fork(this, rej, res); | ||
this._f(rej, res); | ||
@@ -209,3 +176,3 @@ } | ||
function Future$chain(f){ | ||
check$chain(f); | ||
check$chain(this, f); | ||
const _this = this; | ||
@@ -221,4 +188,16 @@ return new FutureClass(function Future$chain$fork(rej, res){ | ||
function Future$chainRej(f){ | ||
check$chainRej(this, f); | ||
const _this = this; | ||
return new FutureClass(function Future$chainRej$fork(rej, res){ | ||
_this._f(function Future$chainRej$rej(x){ | ||
const m = f(x); | ||
check$chainRej$f(m, f, x); | ||
m._f(rej, res); | ||
}, res); | ||
}); | ||
} | ||
function Future$map(f){ | ||
check$map(f); | ||
check$map(this, f); | ||
const _this = this; | ||
@@ -233,3 +212,3 @@ return new FutureClass(function Future$map$fork(rej, res){ | ||
function Future$ap(m){ | ||
check$ap(m); | ||
check$ap(this, m); | ||
const _this = this; | ||
@@ -256,2 +235,13 @@ return new FutureClass(function Future$ap$fork(g, h){ | ||
function Future$race(m){ | ||
check$race(this, m); | ||
const _this = this; | ||
return new FutureClass(function Future$race$fork(rej, res){ | ||
let settled = false; | ||
const once = f => a => settled || (settled = true, f(a)); | ||
_this._f(once(rej), once(res)); | ||
m._f(once(rej), once(res)); | ||
}); | ||
} | ||
//Give Future a prototype. | ||
@@ -265,2 +255,3 @@ FutureClass.prototype = Future.prototype = { | ||
chain: Future$chain, | ||
chainRej: Future$chainRej, | ||
[FL.map]: Future$map, | ||
@@ -270,3 +261,4 @@ map: Future$map, | ||
ap: Future$ap, | ||
toString: Future$toString | ||
toString: Future$toString, | ||
race: Future$race | ||
}; | ||
@@ -280,2 +272,47 @@ | ||
///////////////// | ||
// Dispatchers // | ||
///////////////// | ||
//Creates a dispatcher for a unary method. | ||
function createUnaryDispatcher(method){ | ||
return curry(function dispatch(a, m){ | ||
if(m && typeof m[method] === 'function') return m[method](a); | ||
error$invalidArgument(`Future.${method}`, 1, `have a "${method}" method`, m); | ||
}); | ||
} | ||
//Creates a dispatcher for a binary method. | ||
function createBinaryDispatcher(method){ | ||
return curry(function dispatch(a, b, m){ | ||
if(m && typeof m[method] === 'function') return m[method](a, b); | ||
error$invalidArgument(`Future.${method}`, 2, `have a "${method}" method`, m); | ||
}); | ||
} | ||
//chain :: Chain m => (a -> m b) -> m a -> m b | ||
Future.chain = createUnaryDispatcher('chain'); | ||
//chainRej :: (a -> Future a c) -> Future a b -> Future a c | ||
Future.chainRej = createUnaryDispatcher('chainRej'); | ||
//map :: Functor m => (a -> b) -> m a -> m b | ||
Future.map = createUnaryDispatcher('map'); | ||
//ap :: Apply m => m (a -> b) -> m a -> m b | ||
Future.ap = curry(function dispatch$ap(m, a){ | ||
if(m && typeof m.ap === 'function') return m.ap(a); | ||
error$invalidArgument('Future.ap', 0, 'have a "ap" method', m); | ||
}); | ||
//fork :: (a -> Void) -> (b -> Void) -> Future a b -> Void | ||
Future.fork = createBinaryDispatcher('fork'); | ||
//race :: Future a b -> Future a b -> Future a b | ||
Future.race = createUnaryDispatcher('race'); | ||
/////////////// | ||
// Utilities // | ||
/////////////// | ||
/** | ||
@@ -318,47 +355,2 @@ * Cache a Future | ||
/** | ||
* Turn a node continuation-passing-style function into a function which returns a Future. | ||
* | ||
* Takes a function which uses a node-style callback for continuation and | ||
* returns a function which returns a Future for continuation. | ||
* | ||
* @sig liftNode :: (x..., (a, b -> Void) -> Void) -> x... -> Future[a, b] | ||
* | ||
* @param {Function} f The node function to wrap. | ||
* | ||
* @return {Function} A function which returns a Future. | ||
*/ | ||
Future.liftNode = function Future$liftNode(f){ | ||
check$liftNode(f); | ||
return function Future$liftNode$lifted(){ | ||
const xs = arguments; | ||
return new FutureClass(function Future$liftNode$fork(rej, res){ | ||
return f(...xs, function Future$liftNode$callback(err, result){ | ||
err ? rej(err) : res(result); | ||
}); | ||
}); | ||
}; | ||
}; | ||
/** | ||
* Turn a function which returns a Promise into a function which returns a Future. | ||
* | ||
* @sig liftPromise :: (x... -> Promise a b) -> x... -> Future a b | ||
* | ||
* @param {Function} f The function to wrap. | ||
* | ||
* @return {Function} A function which returns a Future. | ||
*/ | ||
Future.liftPromise = function Future$liftPromise(f){ | ||
check$liftPromise(f); | ||
return function Future$liftPromise$lifted(){ | ||
const xs = arguments; | ||
return new FutureClass(function Future$liftPromise$fork(rej, res){ | ||
const m = f(...xs); | ||
check$liftPromise$f(m, f, xs); | ||
return m.then(res, rej); | ||
}); | ||
}; | ||
}; | ||
//Create a Future which rejects witth the given value. | ||
@@ -415,32 +407,2 @@ Future.reject = function Future$reject(x){ | ||
/** | ||
* 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. | ||
* | ||
* @param {Future} m1 The first Future. | ||
* @param {Future} m2 The second Future. | ||
* | ||
* @return {Future} | ||
* | ||
* @example | ||
* | ||
* race( | ||
* Future(rej => setTimeout(rej, 8000, new Error('Request timed out'))), | ||
* fromNode(done => request('http://example.com', done)) | ||
* ) | ||
* | ||
*/ | ||
Future.race = curry(function Future$race(m1, m2){ | ||
check$race$m1(m1); | ||
check$race$m2(m2); | ||
return new FutureClass(function Future$race$fork(rej, res){ | ||
let settled = false; | ||
const once = f => a => settled || (settled = true, f(a)); | ||
m1._f(once(rej), once(res)); | ||
m2._f(once(rej), once(res)); | ||
}); | ||
}); | ||
//Export Future factory. | ||
@@ -447,0 +409,0 @@ return Future; |
{ | ||
"name": "fluture", | ||
"version": "0.4.1", | ||
"version": "0.5.0", | ||
"description": "A complete Fantasy Land compatible Future library", | ||
@@ -5,0 +5,0 @@ "main": "fluture.js", |
171
README.md
# Fluture | ||
A complete Fantasy Land compatible Future library. | ||
A complete [Fantasy Land][1] compatible Future library. | ||
@@ -11,23 +11,45 @@ > `npm install --save fluture` <sup>Requires a node 5.0.0 compatible environment | ||
Using the low level, high performance method API: | ||
```js | ||
Future.node(done => fs.readFile('package.json', 'utf8', done)) | ||
.chain(x => Future.try(() => JSON.parse(x))) | ||
.map(x => x.name) | ||
.fork(console.error, console.log) | ||
const Future = require('fluture'); | ||
const program = file => | ||
Future.node(done => fs.readFile(file, 'utf8', done)) | ||
.chain(x => Future.try(() => JSON.parse(x))) | ||
.map(x => x.name) | ||
.fork(console.error, console.log); | ||
program('package.json'); | ||
//> "fluture" | ||
``` | ||
## Motivation | ||
Or use the high level, fully curried, functional dispatch API for function | ||
composition using composers like [`S.pipe`][2]: | ||
* A stand-alone Fantasy Future package. | ||
```js | ||
const {node, chain, try, map, fork} = require('fluture'); | ||
const program = S.pipe([ | ||
file => node(fs.readFile.bind(fs, file, 'utf8')), | ||
chain(x => try(() => JSON.parse(x))), | ||
map(x => x.name), | ||
fork(console.error, console.log) | ||
]); | ||
program('package.json') | ||
//> "fluture" | ||
``` | ||
## Why? | ||
* Great error messages. | ||
* Async control utilities included. | ||
* Easier debugging than `f(...).fork is not a function`. | ||
* Still maintain decent speed. | ||
* High performance Future implementation. | ||
* Decent documentation. | ||
## Documentation | ||
### Future | ||
### Constructors | ||
#### Future :: ((a -> Void), (b -> Void) -> Void) -> Future a b | ||
A (monadic) container which represents an eventual value. A lot like Promise but | ||
more principled in that it follows the Fantasy Land algebraic JavaScript | ||
more principled in that it follows the [Fantasy Land][1] algebraic JavaScript | ||
specification. | ||
@@ -44,4 +66,2 @@ | ||
---- | ||
#### `of :: b -> Future a b` | ||
@@ -114,4 +134,26 @@ | ||
---- | ||
### Method API | ||
#### `fork :: Future a b ~> (a -> Void), (b -> Void) -> Void` | ||
Execute the Future (which up until now was merely a container for its | ||
computation), and get at the result or error. | ||
It is the return from Fantasy Land to the real world. This function best shows | ||
the fundamental difference between Promises and Futures. | ||
```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!" | ||
``` | ||
#### `map :: Future a b ~> (b -> c) -> Future a c` | ||
@@ -137,2 +179,16 @@ | ||
#### `chainRej :: Future a b ~> (a -> Future a c) -> Future a c` | ||
FlatMap over the **rejection** value inside the Future. If the Future is | ||
resolved, chaining is not performed. | ||
```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 :: Future a (b -> c) ~> Future a b -> Future a c` | ||
@@ -148,6 +204,4 @@ | ||
---- | ||
#### `race :: Future a b ~> Future a b -> Future a b` | ||
#### `race :: Future a b -> Future a b -> Future a b` | ||
Race two Futures against eachother. Creates a new Future which resolves or | ||
@@ -157,6 +211,38 @@ rejects with the resolution or rejection value of the first Future to settle. | ||
```js | ||
Future.race(Future.after(100, 'hello'), Future.after(50, 'bye')) | ||
Future.after(100, 'hello') | ||
.race(Future.after(50, 'bye')) | ||
.fork(console.error, console.log) | ||
//> "bye" | ||
``` | ||
### 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. | ||
#### `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`][3]. | ||
#### `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`][4]. | ||
#### `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`][5]. | ||
#### `race :: Future a b -> Future a b -> Future a b` | ||
Dispatches the first argument to the `race` method of the second argument. | ||
```js | ||
const first = futures => futures.reduce(Future.race); | ||
@@ -172,13 +258,11 @@ first([ | ||
---- | ||
### Futurization | ||
#### `liftNode :: (x..., (a, b -> Void) -> Void) -> x... -> Future a b` | ||
To reduce the boilerplate of making Node or Promise functions return Future's | ||
instead, one might use the [Futurize][6] library: | ||
Turn a node continuation-passing-style function into a function which returns a Future. | ||
Takes a function which uses a node-style callback for continuation and returns a | ||
function which returns a Future for continuation. | ||
```js | ||
const readFile = Future.liftNode(fs.readFile); | ||
const Future = require('fluture'); | ||
const futurize = require('futurize').futurize(Future); | ||
const readFile = futurize(require('fs').readFile); | ||
readFile('README.md', 'utf8') | ||
@@ -191,8 +275,2 @@ .map(text => text.split('\n')) | ||
#### `liftPromise :: (x... -> Promise a b) -> x... -> Future a b` | ||
Turn a function which returns a Promise into a function which returns a Future. | ||
Like liftNode, but for a function which returns a Promise. | ||
## Road map | ||
@@ -205,13 +283,17 @@ | ||
* [x] Implement Future.cache | ||
* [ ] Implement Future.mapRej | ||
* [ ] Implement Future.chainRej | ||
* [ ] Implement dispatchers: chain, map, ap, fork | ||
* [ ] Implement Future.swap | ||
* [ ] Implement Future.and | ||
* [ ] Implement Future.or | ||
* [x] Implement Future.race | ||
* [ ] Implement Future#mapRej | ||
* [x] Implement Future#chainRej | ||
* [x] Implement dispatchers | ||
* [ ] Implement Future#swap | ||
* [ ] Implement Future#and | ||
* [ ] Implement Future#or | ||
* [ ] Implement Future#fold | ||
* [ ] Implement Future#value | ||
* [x] Implement Future#race | ||
* [ ] Implement Future.parallel | ||
* [ ] Implement Future.predicate | ||
* [ ] Implement Future.promise | ||
* [ ] Implement Future#promise | ||
* [ ] Implement Future.cast | ||
* [ ] Implement Future.encase | ||
* [x] Check `this` on instance methods | ||
* [x] Create documentation | ||
@@ -239,1 +321,10 @@ * [ ] Wiki: Comparison between Future libs | ||
[MIT licensed](LICENSE) | ||
<!-- References --> | ||
[1]: https://github.com/fantasyland/fantasy-land | ||
[2]: https://github.com/plaid/sanctuary#pipe--a-bb-cm-n---a---n | ||
[3]: http://ramdajs.com/docs/#map | ||
[4]: http://ramdajs.com/docs/#chain | ||
[5]: http://ramdajs.com/docs/#ap | ||
[6]: https://github.com/futurize/futurize |
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
22903
321
340