fluture
Advanced tools
Comparing version 0.3.1 to 0.4.0
197
fluture.js
@@ -75,3 +75,3 @@ /*global define*/ | ||
function check$chain$f(m, f, x){ | ||
if(!m || typeof m.fork !== 'function') throw new TypeError(error( | ||
if(!(m instanceof FutureClass)) throw new TypeError(error( | ||
'Future#chain expects the function its given to return a Future', | ||
@@ -92,3 +92,3 @@ `${toString(m)}\n From calling: ${toString(f)}\n With: ${toString(x)}` | ||
function check$ap(m){ | ||
if(!m || typeof m.fork !== 'function') throw new TypeError(error( | ||
if(!(m instanceof FutureClass)) throw new TypeError(error( | ||
'Future#ap expects its argument to be a Future', | ||
@@ -107,5 +107,61 @@ toString(m) | ||
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) | ||
)); | ||
} | ||
function check$try(f){ | ||
if(typeof f !== 'function') throw new TypeError(error( | ||
'Future.try expects its first argument to be a function', | ||
toString(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) | ||
)); | ||
} | ||
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) | ||
)); | ||
} | ||
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) | ||
)); | ||
} | ||
//The of method. | ||
function Future$of(x){ | ||
return new Future(function Future$of$fork(rej, res){ | ||
return new FutureClass(function Future$of$fork(rej, res){ | ||
res(x) | ||
@@ -116,4 +172,3 @@ }); | ||
//Constructor. | ||
function Future(f){ | ||
check$Future(f); | ||
function FutureClass(f){ | ||
this._f = f; | ||
@@ -123,8 +178,9 @@ } | ||
//A createFuture function which pretends to be Future. | ||
function createFuture(f){ | ||
return new Future(f); | ||
function Future(f){ | ||
check$Future(f); | ||
return new FutureClass(f); | ||
} | ||
//Give Future a prototype. | ||
Future.prototype = createFuture.prototype = { | ||
FutureClass.prototype = Future.prototype = { | ||
@@ -144,7 +200,7 @@ _f: null, | ||
const _this = this; | ||
return new Future(function Future$chain$fork(rej, res){ | ||
_this.fork(rej, function Future$chain$res(x){ | ||
return new FutureClass(function Future$chain$fork(rej, res){ | ||
_this._f(rej, function Future$chain$res(x){ | ||
const m = f(x); | ||
check$chain$f(m, f, x); | ||
m.fork(rej, res); | ||
m._f(rej, res); | ||
}); | ||
@@ -157,4 +213,4 @@ }); | ||
const _this = this; | ||
return new Future(function Future$map$fork(rej, res){ | ||
_this.fork(rej, function Future$map$res(x){ | ||
return new FutureClass(function Future$map$fork(rej, res){ | ||
_this._f(rej, function Future$map$res(x){ | ||
res(f(x)); | ||
@@ -168,6 +224,6 @@ }); | ||
const _this = this; | ||
return new Future(function Future$ap$fork(g, h){ | ||
return new FutureClass(function Future$ap$fork(g, h){ | ||
let _f, _x, ok1, ok2, ko; | ||
const rej = x => ko || (ko = 1, g(x)); | ||
_this.fork(rej, function Future$ap$resThis(f){ | ||
_this._f(rej, function Future$ap$resThis(f){ | ||
if(!ok2) return void (ok1 = 1, _f = f); | ||
@@ -177,3 +233,3 @@ check$ap$f(f); | ||
}); | ||
m.fork(rej, function Future$ap$resThat(x){ | ||
m._f(rej, function Future$ap$resThat(x){ | ||
if(!ok1) return void (ok2 = 1, _x = x) | ||
@@ -193,12 +249,24 @@ check$ap$f(_f); | ||
//Expose `of` statically as well. | ||
Future[FL.of] = createFuture[FL.of] = Future$of; | ||
Future[FL.of] = Future[FL.of] = Future$of; | ||
//Expose Future statically for ease of destructuring. | ||
createFuture.Future = Future; | ||
Future.Future = Future; | ||
//Turn a continuation-passing-style function into a function which returns a Future. | ||
createFuture.liftNode = function Future$liftNode(f){ | ||
/** | ||
* 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 Future(function Future$liftNode$fork(rej, res){ | ||
return new FutureClass(function Future$liftNode$fork(rej, res){ | ||
return f(...xs, function Future$liftNode$callback(err, result){ | ||
@@ -211,8 +279,19 @@ err ? rej(err) : res(result); | ||
//Turn a function which returns a Promise into a function which returns a Future. | ||
createFuture.liftPromise = function Future$liftPromise(f){ | ||
/** | ||
* Turn a function which returns a Promise into a function which returns a Future. | ||
* | ||
* @sig liftPromise :: (x... -> a) -> x... -> Future[Error, a] | ||
* | ||
* @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 Future(function Future$liftPromise$fork(rej, res){ | ||
return f(...xs).then(res, rej); | ||
return new FutureClass(function Future$liftPromise$fork(rej, res){ | ||
const m = f(...xs); | ||
check$liftPromise$f(m, f, xs); | ||
return m.then(res, rej); | ||
}); | ||
@@ -223,4 +302,4 @@ }; | ||
//Create a Future which rejects witth the given value. | ||
createFuture.reject = function Future$reject(x){ | ||
return new Future(function Future$reject$fork(rej){ | ||
Future.reject = function Future$reject(x){ | ||
return new FutureClass(function Future$reject$fork(rej){ | ||
rej(x); | ||
@@ -231,4 +310,5 @@ }); | ||
//Create a Future which resolves after the given time with the given value. | ||
createFuture.after = curry(function Future$after(n, x){ | ||
return new Future(function Future$after$fork(rej, res){ | ||
Future.after = curry(function Future$after(n, x){ | ||
check$after(n); | ||
return new FutureClass(function Future$after$fork(rej, res){ | ||
setTimeout(res, n, x); | ||
@@ -240,4 +320,5 @@ }) | ||
//or rejects with the exception thrown by the given function. | ||
createFuture.try = function Future$try(f){ | ||
return new Future(function Future$try$fork(rej, res){ | ||
Future.try = function Future$try(f){ | ||
check$try(f); | ||
return new FutureClass(function Future$try$fork(rej, res){ | ||
try{ | ||
@@ -252,5 +333,57 @@ res(f()); | ||
/** | ||
* Allow one-off wrapping of a function that requires a node-style callback. | ||
* | ||
* @sig fromNode :: ((err, a) -> Void) -> Future[Error, a] | ||
* | ||
* @param {Function} f The operation expected to eventaully call the callback. | ||
* | ||
* @return {Future} | ||
* | ||
* @example | ||
* | ||
* node(done => MySql.connect(done)) | ||
* .fork(console.error, console.log) | ||
* | ||
*/ | ||
Future.node = function Future$node(f){ | ||
check$node(f); | ||
return new FutureClass(function Future$node$fork(rej, res){ | ||
f((a, b) => a ? rej(a) : res(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. | ||
* | ||
* @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. | ||
return createFuture; | ||
return Future; | ||
})); |
{ | ||
"name": "fluture", | ||
"version": "0.3.1", | ||
"version": "0.4.0", | ||
"description": "A complete Fantasy Land compatible Future library", | ||
@@ -5,0 +5,0 @@ "main": "fluture.js", |
143
README.md
@@ -7,2 +7,13 @@ # Fluture | ||
## Usage | ||
```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) | ||
//> "fluture" | ||
``` | ||
## Motivation | ||
@@ -15,2 +26,120 @@ | ||
## Documentation | ||
### Future | ||
A (monadic) container which represents an eventual value. A lot like Promise but | ||
more principled in that it follows the Fantasy Land algebraic JavaScript | ||
specification. | ||
```js | ||
const eventualThing = Future((reject, resolve) => { | ||
setTimeout(resolve, 500, 'world'); | ||
}); | ||
eventualThing.fork(console.error, thing => console.log(`Hello ${thing}!`)); | ||
//> "Hello world!" | ||
``` | ||
---- | ||
#### `of :: b -> Future a b` | ||
A constructor that creates a Future containing the given value. | ||
```js | ||
const eventualThing = Future.of('world'); | ||
eventualThing.fork(console.error, thing => console.log(`Hello ${thing}!`)); | ||
//> "Hello world!" | ||
``` | ||
#### `after :: Number -> b -> Future a b` | ||
A constructor that creates a Future containing the given value after n milliseconds. | ||
```js | ||
const eventualThing = Future.after(500, 'world'); | ||
eventualThing.fork(console.error, thing => console.log(`Hello ${thing}!`)); | ||
//> "Hello world!" | ||
``` | ||
#### `try :: (Void -> !a | b) -> Future a b` | ||
A constructor that creates a Future which resolves with the result of calling | ||
the given function, or rejects with the error thrown by the given function. | ||
```js | ||
const data = {foo: 'bar'} | ||
Future.try(() => data.foo.bar.baz).fork(console.error, console.log) | ||
//> [TypeError: Cannot read property 'baz' of undefined] | ||
``` | ||
#### `node :: ((a, b -> Void) -> Void) -> Future a b` | ||
A constructor that 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. | ||
```js | ||
Future.node(done => fs.readFile('package.json', 'utf8', done)) | ||
.fork(console.error, console.log) | ||
//> "{...}" | ||
``` | ||
---- | ||
#### `map :: Future a b ~> (b -> c) -> Future a c` | ||
Map over the value inside the Future. If the Future is rejected, mapping is not | ||
performed. | ||
```js | ||
Future.of(1).map(x => x + 1).fork(console.error, console.log); | ||
//> 2 | ||
``` | ||
#### `chain :: Future a b ~> (b -> Future a c) -> Future a c` | ||
FlatMap over the value inside the Future. If the Future is rejected, chaining is | ||
not performed. | ||
```js | ||
Future.of(1).chain(x => Future.of(x + 1)).fork(console.error, console.log); | ||
//> 2 | ||
``` | ||
#### `ap :: Future a (b -> c) ~> Future a b -> Future a c` | ||
Apply the value in the Future to the value in the given Future. If the Future is | ||
rejected, applying is not performed. | ||
```js | ||
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. | ||
```js | ||
Future.race(Future.after(100, 'hello'), Future.after(50, 'bye')) | ||
.fork(console.error, console.log) | ||
//> "bye" | ||
const first = futures => futures.reduce(Future.race); | ||
first([ | ||
Future.after(100, 'hello'), | ||
Future.after(50, 'bye'), | ||
Future(rej => setTimeout(rej, 25, 'nope')) | ||
]) | ||
.fork(console.error, console.log) | ||
//> [Error nope] | ||
``` | ||
## Road map | ||
@@ -28,8 +157,18 @@ | ||
* [ ] Implement Future.or | ||
* [ ] Implement Future.race | ||
* [x] Implement Future.race | ||
* [ ] Implement Future.parallel | ||
* [ ] Create documentation | ||
* [x] Create documentation | ||
* [ ] Wiki: Comparison between Future libs | ||
* [ ] Wiki: Comparison Future and Promise | ||
* [ ] Add test coverage | ||
* [ ] A transpiled ES5 version if demand arises | ||
## 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 | ||
@@ -36,0 +175,0 @@ |
16006
327
175