Comparing version 0.5.7 to 0.6.0-beta1
265
fluture.js
@@ -23,2 +23,12 @@ /*global define FantasyLand inspectf*/ | ||
///////////// | ||
// Helpers // | ||
///////////// | ||
function noop(){} | ||
function fork(m, rej, res){ | ||
const clean = m._f(rej, res); | ||
return typeof clean === 'function' ? clean : noop; | ||
} | ||
/////////////////// | ||
@@ -44,2 +54,6 @@ // Type checking // | ||
function isBoolean(b){ | ||
return typeof b === 'boolean'; | ||
} | ||
//////////////////// | ||
@@ -90,4 +104,5 @@ // Error handling // | ||
function check$Future(fork){ | ||
if(!isFunction(fork)) error$invalidArgument('Future', 0, 'be a function', fork); | ||
function check$Future(f, b){ | ||
if(!isFunction(f)) error$invalidArgument('Future', 0, 'be a function', f); | ||
if(!isBoolean(b)) error$invalidArgument('Future', 0, 'be a boolean', b); | ||
} | ||
@@ -135,3 +150,2 @@ | ||
//Check resolution value of the Future on which #ap was called. | ||
function check$ap$f(f){ | ||
@@ -173,8 +187,8 @@ if(!isFunction(f)) throw new TypeError( | ||
function check$cache$settle(oldState, newState, oldValue, newValue){ | ||
if(oldState > 1) throw new Error( | ||
'Future.cache expects the Future it wraps to only resolve or reject once; ' | ||
+ ' a cached Future tried to ' + (newState === 2 ? 'reject' : 'resolve') + ' a second time.' | ||
if(oldState !== 'pending') throw new Error( | ||
'A cached Future may only resolve or reject once;' | ||
+ ` tried to go into a ${newState} state while already ${oldState}.` | ||
+ ' Please check your cached Future and make sure it does not call res or rej multiple times' | ||
+ '\n It was ' + (oldState === 2 ? 'rejected' : 'resolved') + ' with: ' + show(oldValue) | ||
+ '\n It got ' + (newState === 2 ? 'rejected' : 'resolved') + ' with: ' + show(newValue) | ||
+ `\n It was ${oldState} with: ${show(oldValue)}` | ||
+ `\n It got ${newState} with: ${show(newValue)}` | ||
); | ||
@@ -215,14 +229,13 @@ } | ||
//Constructor. | ||
function FutureClass(f){ | ||
function FutureClass(f, b){ | ||
this._f = f; | ||
this._b = b; | ||
} | ||
//A createFuture function which pretends to be Future. | ||
function Future(f){ | ||
check$Future(f); | ||
return new FutureClass(f); | ||
function Future(f, b){ | ||
if(arguments.length < 2) b = true; | ||
check$Future(f, b); | ||
return new FutureClass(f, b); | ||
} | ||
//The of method. | ||
function Future$of(x){ | ||
@@ -236,3 +249,13 @@ return new FutureClass(function Future$of$fork(rej, res){ | ||
check$fork(this, rej, res); | ||
this._f(rej, res); | ||
let immediate = false, clear; | ||
const autoclear = this._b; | ||
clear = fork(this, function Future$fork$rej(x){ | ||
rej(x); | ||
autoclear && clear ? clear() : (immediate = true); | ||
}, function Future$fork$res(x){ | ||
res(x); | ||
autoclear && clear ? clear() : (immediate = true); | ||
}); | ||
autoclear && immediate && clear(); | ||
return clear; | ||
} | ||
@@ -244,8 +267,17 @@ | ||
return new FutureClass(function Future$chain$fork(rej, res){ | ||
_this._f(rej, function Future$chain$res(x){ | ||
let cleared = false, clearThat = noop; | ||
const clearThis = fork(_this, function Future$chain$rej(x){ | ||
cleared || rej(x); | ||
}, function Future$chain$res(x){ | ||
if(cleared) return; | ||
const m = f(x); | ||
check$chain$f(m, f, x); | ||
m._f(rej, res); | ||
clearThat = fork(m, e => cleared || rej(e), x => cleared || res(x)); | ||
}); | ||
}); | ||
return function Future$chain$clear(){ | ||
cleared = true; | ||
clearThis(); | ||
clearThat(); | ||
}; | ||
}, this._b); | ||
} | ||
@@ -257,8 +289,17 @@ | ||
return new FutureClass(function Future$chainRej$fork(rej, res){ | ||
_this._f(function Future$chainRej$rej(x){ | ||
let cleared = false, clearThat = noop; | ||
const clearThis = fork(_this, function Future$chainRej$rej(x){ | ||
if(cleared) return; | ||
const m = f(x); | ||
check$chainRej$f(m, f, x); | ||
m._f(rej, res); | ||
}, res); | ||
}); | ||
clearThat = fork(m, e => cleared || rej(e), x => cleared || res(x)); | ||
}, function Future$chainRej$res(x){ | ||
cleared || res(x); | ||
}); | ||
return function Future$chainRej$clear(){ | ||
cleared = true; | ||
clearThis(); | ||
clearThat(); | ||
}; | ||
}, _this._b); | ||
} | ||
@@ -270,6 +311,13 @@ | ||
return new FutureClass(function Future$map$fork(rej, res){ | ||
_this._f(rej, function Future$map$res(x){ | ||
res(f(x)); | ||
let cleared = false; | ||
const clear = fork(_this, function Future$map$rej(x){ | ||
cleared || rej(x); | ||
}, function Future$map$res(x){ | ||
cleared || res(f(x)); | ||
}); | ||
}); | ||
return function Future$map$clear(){ | ||
cleared = true; | ||
clear(); | ||
}; | ||
}, _this._b); | ||
} | ||
@@ -281,5 +329,6 @@ | ||
return new FutureClass(function Future$ap$fork(g, res){ | ||
let _f, _x, ok1, ok2, ko; | ||
const rej = x => ko || (ko = 1, g(x)); | ||
_this._f(rej, function Future$ap$resThis(f){ | ||
let _f, _x, ok1, ok2, ko, cleared = false; | ||
const rej = x => cleared || ko || (ko = 1, g(x)); | ||
const clearThis = fork(_this, rej, function Future$ap$resThis(f){ | ||
if(cleared) return; | ||
if(!ok2) return void (ok1 = 1, _f = f); | ||
@@ -289,3 +338,4 @@ check$ap$f(f); | ||
}); | ||
m._f(rej, function Future$ap$resThat(x){ | ||
const clearThat = fork(m, rej, function Future$ap$resThat(x){ | ||
if(cleared) return; | ||
if(!ok1) return void (ok2 = 1, _x = x) | ||
@@ -295,3 +345,8 @@ check$ap$f(_f); | ||
}); | ||
}); | ||
return function Future$ap$clear(){ | ||
cleared = true; | ||
clearThis(); | ||
clearThat(); | ||
}; | ||
}, _this._b); | ||
} | ||
@@ -307,7 +362,12 @@ | ||
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)); | ||
}); | ||
let settled = false, cleared = false; | ||
const once = f => a => cleared || settled || (settled = true, f(a)); | ||
const clearThis = fork(_this, once(rej), once(res)); | ||
const clearThat = fork(m, once(rej), once(res)); | ||
return function Future$race$clear(){ | ||
cleared = true; | ||
clearThis(); | ||
clearThat(); | ||
}; | ||
}, _this._b); | ||
} | ||
@@ -319,12 +379,17 @@ | ||
return new FutureClass(function Future$or$fork(rej, res){ | ||
let ok = false, ko = false, val, err; | ||
_this._f( | ||
() => ko ? rej(err) : ok ? res(val) : (ko = true), | ||
x => (ok = true, res(x)) | ||
let ok = false, ko = false, val, err, cleared = false; | ||
const clearThis = fork(_this, | ||
() => cleared || ko ? rej(err) : ok ? res(val) : (ko = true), | ||
x => cleared || (ok = true, res(x)) | ||
); | ||
m._f( | ||
e => ok || (ko ? rej(e) : (err = e, ko = true)), | ||
x => ok || (ko ? res(x) : (val = x, ok = true)) | ||
const clearThat = fork(m, | ||
e => cleared || ok || (ko ? rej(e) : (err = e, ko = true)), | ||
x => cleared || ok || (ko ? res(x) : (val = x, ok = true)) | ||
); | ||
}); | ||
return function Future$or$clear(){ | ||
cleared = true; | ||
clearThis(); | ||
clearThat(); | ||
}; | ||
}, _this._b); | ||
} | ||
@@ -336,4 +401,13 @@ | ||
return new FutureClass(function Future$fold$fork(rej, res){ | ||
_this._f(e => res(f(e)), x => res(g(x))); | ||
}); | ||
let cleared = false; | ||
const clear = fork(_this, function Future$fold$rej(e){ | ||
cleared || res(f(e)); | ||
}, function Future$fold$res(x){ | ||
cleared || res(g(x)); | ||
}); | ||
return function Future$fold$clear(){ | ||
cleared = true; | ||
clear(); | ||
}; | ||
}, _this._b); | ||
} | ||
@@ -343,3 +417,3 @@ | ||
check$value(this, f); | ||
this._f( | ||
return this.fork( | ||
function Future$value$rej(e){ | ||
@@ -365,5 +439,5 @@ throw new Error( | ||
const _this = this; | ||
let que = []; | ||
let value, state; | ||
let que = [], value, state = 'idle'; | ||
const settleWith = newState => function Future$cache$settle(newValue){ | ||
if(state === 'idle') return; | ||
check$cache$settle(state, newState, value, newValue); | ||
@@ -378,12 +452,19 @@ value = newValue; state = newState; | ||
return new FutureClass(function Future$cache$fork(rej, res){ | ||
let clear = noop; | ||
switch(state){ | ||
case 1: que.push({2: rej, 3: res}); break; | ||
case 2: rej(value); break; | ||
case 3: res(value); break; | ||
default: | ||
state = 1; | ||
que.push({2: rej, 3: res}); | ||
_this.fork(settleWith(2), settleWith(3)); | ||
case 'pending': que.push({rejected: rej, resolved: res}); break; | ||
case 'rejected': rej(value); break; | ||
case 'resolved': res(value); break; | ||
case 'idle': | ||
state = 'pending'; | ||
que.push({rejected: rej, resolved: res}); | ||
clear = fork(_this, settleWith('rejected'), settleWith('resolved')); | ||
} | ||
}); | ||
return function Future$cache$clear(){ | ||
state = 'idle'; | ||
value = undefined; | ||
que = []; | ||
clear(); | ||
}; | ||
}, false); | ||
} | ||
@@ -451,39 +532,19 @@ | ||
//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 = function dispatch$ap(m, a){ | ||
if(arguments.length === 1) return a => 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'); | ||
//or :: Future a b -> Future a b -> Future a b | ||
Future.or = createUnaryDispatcher('or'); | ||
//fold :: (a -> c) -> (b -> c) -> Future a b -> Future _ c | ||
Future.fold = createBinaryDispatcher('fold'); | ||
//value :: (b -> Void) -> Future a b -> Void | ||
Future.value = createUnaryDispatcher('value'); | ||
//promise :: Future a b -> Promise b a | ||
Future.promise = createNullaryDispatcher('promise'); | ||
//cache :: Future a b -> Future a b | ||
Future.cache = createNullaryDispatcher('cache'); | ||
Future.ap = function dispatch$ap(m, a){ | ||
if(arguments.length === 1) return a => dispatch$ap(m, a); | ||
if(m && typeof m.ap === 'function') return m.ap(a); | ||
error$invalidArgument('Future.ap', 0, 'have a "ap" method', m); | ||
}; | ||
////////////////// | ||
@@ -493,3 +554,2 @@ // Constructors // | ||
//Create a Future which rejects witth the given value. | ||
Future.reject = function Future$reject(x){ | ||
@@ -501,3 +561,2 @@ return new FutureClass(function Future$reject$fork(rej){ | ||
//Create a Future which resolves after the given time with the given value. | ||
Future.after = function Future$after(n, x){ | ||
@@ -507,3 +566,6 @@ if(arguments.length === 1) return x => Future$after(n, x); | ||
return new FutureClass(function Future$after$fork(rej, res){ | ||
setTimeout(res, n, x); | ||
const id = setTimeout(res, n, x); | ||
return function Future$after$clear(){ | ||
clearTimeout(id); | ||
}; | ||
}) | ||
@@ -513,6 +575,6 @@ }; | ||
Future.cast = function Future$cast(m){ | ||
check$cast(m); | ||
if(m instanceof FutureClass){ | ||
return m; | ||
} | ||
check$cast(m); | ||
return new FutureClass(function Future$cast$fork(rej, res){ | ||
@@ -523,3 +585,2 @@ m.fork(rej, res); | ||
//encase :: (a -> !b | c) -> a -> Future b c | ||
Future.encase = function Future$encase(f, x){ | ||
@@ -540,4 +601,2 @@ if(arguments.length === 1) return x => Future$encase(f, x); | ||
//Create a Future which resolves with the return value of the given function, | ||
//or rejects with the exception thrown by the given function. | ||
Future.try = function Future$try(f){ | ||
@@ -547,3 +606,2 @@ return Future.encase(f, undefined); | ||
//node :: ((err, a) -> Void) -> Future[Error, a] | ||
Future.node = function Future$node(f){ | ||
@@ -556,3 +614,2 @@ check$node(f); | ||
//parallel :: PositiveInteger -> [Future a b] -> Future a [b] | ||
Future.parallel = function Future$parallel(i, ms){ | ||
@@ -563,11 +620,17 @@ if(arguments.length === 1) return ms => Future$parallel(i, ms); | ||
return l < 1 ? Future$of([]) : new FutureClass(function Future$parallel$fork(rej, res){ | ||
let ko = false; | ||
let ok = 0; | ||
const out = new Array(l); | ||
const next = j => i < l ? fork(ms[i], i++) : (j === l && res(out)); | ||
const fork = (m, j) => (check$parallel$m(m, j), m._f( | ||
e => ko || (rej(e), ko = true), | ||
x => ko || (out[j] = x, next(++ok)) | ||
)); | ||
ms.slice(0, i).forEach(fork); | ||
let ko = false, ok = 0, cleared = false; | ||
const clears = [], out = new Array(l); | ||
const next = j => i < l ? run(ms[i], i++) : (j === l && res(out)); | ||
const run = (m, j) => { | ||
check$parallel$m(m, j); | ||
clears.push(fork(m, | ||
e => cleared || ko || (rej(e), ko = true), | ||
x => cleared || ko || (out[j] = x, next(++ok)) | ||
)); | ||
} | ||
ms.slice(0, i).forEach(run); | ||
return function Future$parallel$clear(){ | ||
cleared = true; | ||
clears.forEach(f => f()); | ||
}; | ||
}); | ||
@@ -574,0 +637,0 @@ }; |
{ | ||
"name": "fluture", | ||
"version": "0.5.7", | ||
"version": "0.6.0-beta1", | ||
"description": "A mathematically correct alternative to Promises for asynchronous control flow", | ||
@@ -15,5 +15,6 @@ "main": "fluture.js", | ||
"setup": "npm run post-merge && cp scripts/hooks/* .git/hooks && git config push.followTags true", | ||
"test": "npm run check-version && npm run clean && npm run lint && npm run test:unit", | ||
"test": "npm run check-version && npm run clean && npm run lint && npm run test:coverage", | ||
"test:opt": "node --allow-natives-syntax --trace-opt --trace-deopt --trace-inlining scripts/test-opt", | ||
"test:unit": "node --harmony-destructuring node_modules/.bin/istanbul cover --report html ./node_modules/.bin/_mocha -- --ui bdd --reporter spec --check-leaks --full-trace && codecov" | ||
"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" | ||
}, | ||
@@ -20,0 +21,0 @@ "author": "Aldwin Vlasblom <aldwin.vlasblom@gmail.com> (https://github.com/Avaq)", |
217
README.md
@@ -8,3 +8,3 @@ # Fluture | ||
[![Build Status](https://travis-ci.org/Avaq/Fluture.svg?branch=master)](https://travis-ci.org/Avaq/Fluture) | ||
[![Code Coverage](https://codecov.io/github/Avaq/Fluture/coverage.svg?branch=develop)](https://codecov.io/github/Avaq/Fluture/fluture.js?branch=develop) | ||
[![Code Coverage](https://codecov.io/github/Avaq/Fluture/coverage.svg?branch=master)](https://codecov.io/github/Avaq/Fluture/fluture.js?branch=master) | ||
@@ -19,3 +19,16 @@ Futures are containers which represent some eventual value as a result of an | ||
## Table of contents | ||
- [Usage](#usage) | ||
- [Motivation and Features](#motivation-and-features) | ||
- [Documentation](#documentation) | ||
- [Type signatures](#type-signatures) | ||
- [Constructors](#constructors) | ||
- [Method API](#method-api) | ||
- [Dispatcher API](#dispatcher-api) | ||
- [Cancellation and resource disposal](#cancellation-and-resource-disposal) | ||
- [Futurization](#futurization) | ||
- [Benchmarks](#benchmarks) | ||
- [The name](#the-name) | ||
## Usage | ||
@@ -51,11 +64,17 @@ | ||
## Motivation | ||
## 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. The | ||
library also comes bundled with many **async control utilities**. To prevent | ||
these features from coming at the cost of performance, Fluture was optimized to | ||
operate at **high performance**. For an overview of differences between Fluture | ||
and other Future implementations, look at [this wiki article][15]. | ||
an effort to provide **great error messages** when something goes wrong. Some | ||
other features include: | ||
* Plenty of async control utilities, like | ||
[`Future.parallel`](#parallel--positiveinteger---future-a-b---future-a-b) | ||
and [`Future#or`](#or--future-a-b--future-a-b---future-a-b). | ||
* [Process cancellation and automatic resource disposal](#cancellation-and-resource-disposal). | ||
* High performance. | ||
For an overview of differences between Fluture and other Future implementations, | ||
have a look at [this wiki article][15]. | ||
## Documentation | ||
@@ -81,3 +100,3 @@ | ||
#### `Future :: ((a -> Void), (b -> Void) -> Void) -> Future a b` | ||
#### `Future :: ((a -> ()), (b -> ()) -> ?(() -> ())) -> Future a b` | ||
@@ -144,3 +163,3 @@ A (monadic) container which represents an eventual value. A lot like Promise but | ||
#### `try :: (Void -> !a | b) -> Future a b` | ||
#### `try :: (() -> !a | b) -> Future a b` | ||
@@ -159,3 +178,3 @@ A constructor that creates a Future which resolves with the result of calling | ||
#### `node :: ((a, b -> Void) -> Void) -> Future a b` | ||
#### `node :: ((a, b -> ()) -> ()) -> Future a b` | ||
@@ -218,27 +237,5 @@ A constructor that creates a Future which rejects with the first argument given | ||
#### `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 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); | ||
//> "{...}" | ||
``` | ||
### Method API | ||
#### `fork :: Future a b ~> (a -> Void), (b -> Void) -> Void` | ||
#### `fork :: Future a b ~> (a -> ()), (b -> ()) -> () -> ()` | ||
@@ -371,4 +368,32 @@ Execute the Future (which up until now was merely a container for its | ||
#### `value :: Future a b ~> (b -> Void) -> Void` | ||
#### `cache :: Future a b ~> Future a b` | ||
Returns a cached version of the Future so that whenever it's forked, it can load | ||
the value from cache rather than reexecuting the chain. This means you can use | ||
the same Future in multiple `chain`s, without having to worry that it's going | ||
to re-execute the computation every time. | ||
Please note that cached Futures, and Futures derived from them do not | ||
automatically [dispose of resources](#automatic-resource-disposal). When the | ||
cached Future is [cancelled or disposed manually](#manual-resource-disposal-and-cancellation), | ||
it will clear its internal cache. The next time it's forked *after* that, it | ||
must re-execute the underlying Future to populate its cache again. | ||
```js | ||
const eventualPackage = Future.node(done => { | ||
console.log('Reading some big data'); | ||
fs.readFile('package.json', 'utf8', done) | ||
}) | ||
.cache(); | ||
eventualPackage.fork(console.error, console.log); | ||
//> "Reading some big data" | ||
//> "{...}" | ||
eventualPackage.fork(console.error, console.log); | ||
//> "{...}" | ||
``` | ||
#### `value :: Future a b ~> (b -> ()) -> () -> ()` | ||
Extracts the value from a resolved Future by forking it. Only use this function | ||
@@ -400,3 +425,3 @@ if you are sure the Future is going to be resolved, for example; after using | ||
#### `fork :: (a -> Void) -> (b -> Void) -> Future a b -> Void` | ||
#### `fork :: (a -> ()) -> (b -> ()) -> Future a b -> () -> ()` | ||
@@ -406,4 +431,4 @@ Dispatches the first and second arguments to the `fork` method of the third argument. | ||
```js | ||
const consoleFork = Future.fork(console.error, console.log); | ||
consoleFork(Future.of('Hello')); | ||
const consoleFork = fork(console.error, console.log); | ||
consoleFork(of('Hello')); | ||
//> "Hello" | ||
@@ -436,6 +461,6 @@ ``` | ||
```js | ||
const first = futures => futures.reduce(Future.race); | ||
const first = futures => futures.reduce(race); | ||
first([ | ||
Future.after(100, 'hello'), | ||
Future.after(50, 'bye'), | ||
after(100, 'hello'), | ||
after(50, 'bye'), | ||
Future(rej => setTimeout(rej, 25, 'nope')) | ||
@@ -452,19 +477,10 @@ ]) | ||
```js | ||
const any = futures => futures.reduce(Future.or, Future.reject('Empty list!')); | ||
const program = S.pipe([ | ||
reject, | ||
or(of('second chance')), | ||
value(console.log) | ||
]); | ||
any([ | ||
Future.reject('first: nope'), | ||
Future.of('second: yep'), | ||
Future.reject('third: yep') | ||
]) | ||
.fork(console.error, console.log); | ||
//> "second: yep" | ||
any([ | ||
Future.reject('first: nope'), | ||
Future.reject('second: nope'), | ||
Future.reject('third: nope') | ||
]) | ||
.fork(console.error, console.log); | ||
//> "third: nope" | ||
program('first chance') | ||
> "second chance" | ||
``` | ||
@@ -476,6 +492,93 @@ | ||
#### `value :: (b -> Void) -> Future a b -> Void` | ||
#### `cache :: Future a b -> Future a b` | ||
Dispatches to the `cache` method. | ||
```js | ||
const cachedConnection = Future.cache(mysqlConnect()); | ||
``` | ||
#### `value :: (b -> ()) -> Future a b -> () -> ()` | ||
Dispatches the first argument to the `value` method of the second argument. | ||
#### `promise :: Future a b -> Promise b a` | ||
Dispatches to the `promise` method. | ||
```js | ||
Future.promise(Future.after(300, 'Hello')).then(console.log); | ||
//> "Hello" | ||
``` | ||
### Cancellation and resource disposal | ||
#### Automatic resource disposal | ||
When a Future is created, it is given the `fork` function. This `fork` function | ||
sometimes creates resources, like `setTimeout`s in the event loop or database | ||
connections. In order to deal with the disposal of these resources, one may | ||
*return* a function from `fork`, which will be automatically called after the | ||
Future has forked. This function is expected to be idempotent. | ||
It's the responsibility of this `fork` function to prevent `rej` or `res` from | ||
being called after its returned disposal function has been called. All internal | ||
Fluture functions, for example `Future.after`, play by these rules. | ||
```js | ||
const createDatabaseConnection = settings => Future((rej, res) => { | ||
const conn = mysql.connect(settings, res); | ||
return () => conn.hasEnded || conn.end(); | ||
}); | ||
createDatabaseConnection() | ||
.chain(conn => conn.query('SELECT 1 + 1 AS two')) | ||
//When we `fork`, all of the resource disposers will be automatically called. | ||
.fork(console.error, console.log); | ||
``` | ||
If you don't want a particular Future to automatically dispose of its resources, | ||
you can construct it with `false` passed as its second argument: | ||
```js | ||
Future( | ||
rej => { | ||
const id = setTimeout(rej, 2000, 'timed out') | ||
return () => clearTimeout(id); | ||
}, | ||
false //<--- | ||
) | ||
``` | ||
This Future, and any Future's derived from it through `map`, `chain`, etc. will | ||
no longer automatically dispose of their resources after being forked. This | ||
allows resources to only be disposed manually. | ||
#### Manual resource disposal and cancellation | ||
Both `.fork()` and `.value()` return the disposal function and you can call it | ||
at any time to manually dispose of resources. | ||
Besides resource disposal, this function can also be used to cancel running | ||
Futures. When called prematurely it will dispose the resources and cause the | ||
whole pipeline to come to a halt: | ||
```js | ||
const loadPage = url => Future((rej, res) => { | ||
const req = new XMLHttpRequest(); | ||
req.addEventListener('load', res); | ||
req.addEventListener('error', rej); | ||
req.open('GET', url); | ||
req.send(); | ||
return () => (req.readyState < 4) && req.abort(); | ||
}); | ||
const cancel = loadPage('https://github.com').fork(onFailure, onSuccess); | ||
//Cancel the Future immediately. Nothing will happen; `onFailure` nor | ||
//`onSuccess` will ever be called anymore because `cancel` called `req.abort()`. | ||
cancel(); | ||
``` | ||
### Futurization | ||
@@ -482,0 +585,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
40520
536
621