diff --git a/README.md b/README.md index 87959ceb..f9e59721 100644 --- a/README.md +++ b/README.md @@ -147,7 +147,7 @@ for sponsoring the project. - [`rejectAfter`: Create a Future that rejects after a timeout](#rejectafter) - [`do`: Create a "coroutine" using a generator function](#do) - [`try`: Create a Future using a possibly throwing function](#try) -- [`tryP`: Create a Future using a Promise-returning function](#tryp) +- [`attemptP`: Create a Future using a Promise-returning function](#attemptp) - [`node`: Create a Future using a Node-style callback](#node) - [`encase`: Convert a possibly throwing function to a Future function](#encase) - [`encaseP`: Convert a Promise-returning function to a Future function](#encasep) @@ -165,7 +165,7 @@ for sponsoring the project.
Converting between Promises and Futures -- [`tryP`: Create a Future using a Promise-returning function](#tryp) +- [`attemptP`: Create a Future using a Promise-returning function](#attemptp) - [`encaseP`: Convert a Promise-returning function to a Future function](#encasep) - [`promise`: Convert a Future to a Promise](#promise) @@ -669,12 +669,12 @@ Future.try(() => data.foo.bar.baz) //> [TypeError: Cannot read property 'baz' of undefined] ``` -#### tryP +#### attemptP -
tryP :: (() -> Promise e r) -> Future e r +
attemptP :: (() -> Promise e r) -> Future e r ```hs -tryP :: (() -> Promise e r) -> Future e r +attemptP :: (() -> Promise e r) -> Future e r ```
@@ -685,7 +685,7 @@ resolves with its resolution value, or rejects with its rejection reason. Short for [`Future.encaseP(f, undefined)`](#encasep). ```js -Future.tryP(() => Promise.resolve('Hello')) +Future.attemptP(() => Promise.resolve('Hello')) .fork(console.error, console.log); //> "Hello" ``` @@ -772,7 +772,7 @@ Furthermore; `encaseP2` and `encaseP3` are binary and ternary versions of var fetchf = Future.encaseP(fetch); fetchf('https://api.github.com/users/Avaq') -.chain(res => Future.tryP(_ => res.json())) +.chain(res => Future.attemptP(_ => res.json())) .map(user => user.name) .fork(console.error, console.log); //> "Aldwin Vlasblom" @@ -1105,16 +1105,6 @@ Future.after(300, null) //> "hello" ``` -With good old `reduce`, we can turn this into an asynchronous `all` function, -where the resulting Future will be the leftmost to reject, or the rightmost to -resolve. - -```js -var all = ms => ms.reduce(Future.and, Future.of(0)); -all([Future.after(20, 1), Future.of(2)]).value(console.log); -//> 2 -``` - #### alt
alt :: Alt f => f a -> f a -> f a @@ -1150,16 +1140,6 @@ Future.rejectAfter(300, new Error('Failed')) //> "hello" ``` -With good old `reduce`, we can turn this into an asynchronous `any` function, -where the resulting Future will be the leftmost to resolve, or the rightmost -to reject. - -```js -var any = ms => ms.reduce(Future.alt, Future.reject('empty list')); -any([Future.reject(1), Future.after(20, 2), Future.of(3)]).value(console.log); -//> 2 -``` - #### finally
finally :: Future a c -> Future a b -> Future a b @@ -1430,20 +1410,6 @@ Future.after(100, 'hello') //> "bye" ``` -With good old `reduce`, we can turn this into a `first` function, where the -resulting Future will be the first to resolve, or the first to reject. - -```js -var first = futures => futures.reduce(Future.race, Future.never); -first([ - Future.after(100, 'hello'), - Future.after(50, 'bye'), - Future.rejectAfter(25, 'nope') -]) -.fork(console.error, console.log); -//! "nope" -``` - #### both
both :: Future a b -> Future a c -> Future a (Pair b c) diff --git a/bench/fluture.js b/bench/fluture.js index f8b0faa9..f673e8b1 100644 --- a/bench/fluture.js +++ b/bench/fluture.js @@ -119,6 +119,18 @@ module.exports = require('sanctuary-benchmark')(Old, New, config, { {}, ({of, map}) => run(map(plus1, of(1))) ], + 'run.transform.sync.swap.one': [ + {}, ({of, swap}) => run(swap(of(42))) + ], + + 'run.transform.sync.swap.many': [ + {}, ({of, swap}) => { + let m = of(1); + for(let i = 0; i < 1000; i++) { m = swap(m); } + run(m); + } + ], + 'run.transform.sync.chain.one': [ {}, ({of, chain}) => run(chain(compose(of, plus1), of(1))) ], diff --git a/fluenture.mjs b/fluenture.mjs new file mode 100644 index 00000000..32130d9f --- /dev/null +++ b/fluenture.mjs @@ -0,0 +1,58 @@ +// Future.prototype.pipe = function Future$pipe(f){ +// if(!isFuture(this)) throw invalidContext('Future#pipe', this); +// if(!isFunction(f)) throw invalidArgument('Future#pipe', 0, 'be a Function', f); +// return f(this); +// }; + +// Future.prototype.alt = createFlippedUnaryMethod('alt', alt); +// Future.prototype.and = createFlippedUnaryMethod('and', and); +// Future.prototype.ap = createFlippedUnaryMethod('ap', ap); +// Future.prototype.bimap = createBinaryMethod('bimap', bimap); +// Future.prototype.both = createFlippedUnaryMethod('both', both); +// Future.prototype.chain = createUnaryMethod('chain', chain); +// Future.prototype.chainRej = createUnaryMethod('chainRej', chainRej); +// Future.prototype.finally = createFlippedUnaryMethod('finally', lastly); +// Future.prototype.fold = createBinaryMethod('fold', fold); +// Future.prototype.lastly = createFlippedUnaryMethod('lastly', lastly); +// Future.prototype.map = createUnaryMethod('map', map); +// Future.prototype.mapRej = createUnaryMethod('mapRej', mapRej); +// Future.prototype.or = createFlippedUnaryMethod('or', alt); +// Future.prototype.race = createFlippedUnaryMethod('race', race); +// Future.prototype.swap = createFlippedUnaryMethod('swap', swap); +// Future.prototype.done = createUnaryMethod('done', done); + +// Future.prototype.fork = function Future$fork(rej, res){ +// if(!isFuture(this)) throwInvalidContext('Future#fork', this); +// if(!isFunction(rej)) throwInvalidArgument('Future#fork', 0, 'be a Function', rej); +// if(!isFunction(res)) throwInvalidArgument('Future#fork', 1, 'be a Function', res); +// return this._interpret(raise, rej, res); +// }; + +// Future.prototype.forkCatch = function Future$forkCatch(rec, rej, res){ +// if(!isFuture(this)) throwInvalidContext('Future#forkCatch', this); +// if(!isFunction(rec)) throwInvalidArgument('Future#forkCatch', 0, 'be a Function', rec); +// if(!isFunction(rej)) throwInvalidArgument('Future#forkCatch', 1, 'be a Function', rej); +// if(!isFunction(res)) throwInvalidArgument('Future#forkCatch', 2, 'be a Function', res); +// return this._interpret(rec, rej, res); +// }; + +// Future.prototype.value = function Future$value(res){ +// if(!isFuture(this)) throwInvalidContext('Future#value', this); +// if(!isFunction(res)) throwInvalidArgument('Future#value', 0, 'be a Function', res); +// var _this = this; +// return _this._interpret(raise, function Future$value$rej(x){ +// raise(error( +// 'Future#value was called on a rejected Future\n' + +// ' Rejection: ' + show(x) + '\n' + +// ' Future: ' + _this.toString() +// )); +// }, res); +// }; + +// Future.prototype.promise = function Future$promise(){ +// if(!isFuture(this)) throwInvalidContext('Future#promise', this); +// var _this = this; +// return new Promise(function Future$promise$computation(res, rej){ +// _this._interpret(raise, rej, res); +// }); +// }; diff --git a/index.d.ts b/index.d.ts index 753e2b09..d71612ad 100644 --- a/index.d.ts +++ b/index.d.ts @@ -320,8 +320,8 @@ declare module 'fluture' { /** Swap the rejection reason and the resolution value. See https://github.com/fluture-js/Fluture#swap */ export function swap(source: FutureInstance): FutureInstance - /** Convert a Promise-returning function to a Future. See https://github.com/fluture-js/Fluture#tryP */ - export function tryP(fn: () => Promise): FutureInstance + /** Convert a Promise-returning function to a Future. See https://github.com/fluture-js/Fluture#attemptP */ + export function attemptP(fn: () => Promise): FutureInstance /** Fork the Future into the given continuation. See https://github.com/fluture-js/Fluture#value */ export function value(resolve: ResolveFunction, source: FutureInstance): Cancel diff --git a/index.mjs b/index.mjs index cb6f0d88..e6d2b918 100644 --- a/index.mjs +++ b/index.mjs @@ -1,54 +1,105 @@ import concurrify from 'concurrify'; import type from 'sanctuary-type-identifiers'; -import {throwInvalidArgument} from './src/internal/throw'; -import {Future, resolve, reject, never} from './src/future'; + +import {captureContext} from './src/internal/debug'; +import {invalidArgument} from './src/internal/error'; +import {nil} from './src/internal/list'; import {FL} from './src/internal/const'; + +import {Future, never} from './src/future'; + +import {AltTransformation} from './src/alt'; +import {ApTransformation} from './src/ap'; +import {BimapTransformation} from './src/bimap'; +import {ChainTransformation} from './src/chain'; +import {MapTransformation} from './src/map'; + import {chainRec} from './src/chain-rec'; -import {ap, map, bimap, chain, race, alt} from './src/dispatchers/index'; - -Future.resolve = Future.of = Future[FL.of] = resolve; -Future.chainRec = Future[FL.chainRec] = chainRec; -Future.reject = reject; -Future.ap = ap; -Future.alt = alt; -Future.map = map; -Future.bimap = bimap; -Future.chain = chain; - -var Par = concurrify(Future, never, race, function parallelAp(a, b){ return b._parallelAp(a) }); -Par.of = Par[FL.of]; -Par.zero = Par[FL.zero]; -Par.map = map; -Par.ap = ap; -Par.alt = alt; - -function isParallel(x){ +import {parallelAp} from './src/parallel-ap'; +import {race} from './src/race'; +import {resolve} from './src/resolve'; + +Future[FL.of] = resolve; +Future[FL.chainRec] = chainRec; + +Future.prototype[FL.ap] = function Future$FL$ap(other){ + var context = captureContext(nil, 'a Fantasy Land dispatch to ap', Future$FL$ap); + return other._transform(new ApTransformation(context, this)); +}; + +Future.prototype[FL.map] = function Future$FL$map(mapper){ + var context = captureContext(nil, 'a Fantasy Land dispatch to map', Future$FL$map); + return this._transform(new MapTransformation(context, mapper)); +}; + +Future.prototype[FL.bimap] = function Future$FL$bimap(lmapper, rmapper){ + var context = captureContext(nil, 'a Fantasy Land dispatch to bimap', Future$FL$bimap); + return this._transform(new BimapTransformation(context, lmapper, rmapper)); +}; + +Future.prototype[FL.chain] = function Future$FL$chain(mapper){ + var context = captureContext(nil, 'a Fantasy Land dispatch to chain', Future$FL$chain); + return this._transform(new ChainTransformation(context, mapper)); +}; + +Future.prototype[FL.alt] = function Future$FL$alt(other){ + var context = captureContext(nil, 'a Fantasy Land dispatch to alt', Future$FL$alt); + return this._transform(new AltTransformation(context, other)); +}; + +function uncurry(f){ + return function(a, b){ + return f(a)(b); + }; +} + +export var Par = concurrify(Future, never, uncurry(race), uncurry(parallelAp)); + +export function isParallel(x){ return x instanceof Par || type(x) === Par['@@type']; } -function seq(par){ - if(!isParallel(par)) throwInvalidArgument('seq', 0, 'be a ConcurrentFuture', par); +export function seq(par){ + if(!isParallel(par)) throw invalidArgument('seq', 0, 'be a ConcurrentFuture', par); return par.sequential; } -export {Future, Future as default, Par, isParallel, seq}; -export {isFuture, reject, resolve, resolve as of, never, isNever} from './src/future'; -export * from './src/dispatchers/index'; -export {after, rejectAfter} from './src/after'; -export {attempt, attempt as try} from './src/attempt'; +export {Future as default, Future}; +export {isFuture, never, isNever} from './src/future'; + +export {after} from './src/after'; +export {alt} from './src/alt'; +export {and} from './src/and'; +export {ap} from './src/ap'; +export {attemptP} from './src/attempt-p'; +export {attempt} from './src/attempt'; +export {bimap} from './src/bimap'; +export {both} from './src/both'; export {cache} from './src/cache'; -export {encase} from './src/encase'; -export {encase2} from './src/encase2'; -export {encase3} from './src/encase3'; -export {encaseN} from './src/encase-n'; -export {encaseN2} from './src/encase-n2'; -export {encaseN3} from './src/encase-n3'; +export {chainRej} from './src/chain-rej'; +export {chain} from './src/chain'; +export {done} from './src/done'; export {encaseP} from './src/encase-p'; -export {encaseP2} from './src/encase-p2'; -export {encaseP3} from './src/encase-p3'; -export {go, go as do} from './src/go'; +export {encase} from './src/encase'; +export {extractLeft} from './src/extract-left'; +export {extractRight} from './src/extract-right'; +export {fold} from './src/fold'; +export {forkCatch} from './src/fork-catch'; +export {fork} from './src/fork'; +export {go} from './src/go'; export {hook} from './src/hook'; +export {lastly} from './src/lastly'; +export {mapRej} from './src/map-rej'; +export {map} from './src/map'; export {node} from './src/node'; +export {parallelAp} from './src/parallel-ap'; export {parallel} from './src/parallel'; -export {tryP} from './src/try-p'; +export {promise} from './src/promise'; +export {race} from './src/race'; +export {rejectAfter} from './src/reject-after'; +export {reject} from './src/reject'; +export {resolve} from './src/resolve'; +export {swap} from './src/swap'; +export {value} from './src/value'; + export {debugMode} from './src/internal/debug'; diff --git a/package.json b/package.json index 39c7a18c..9e8d5c6d 100644 --- a/package.json +++ b/package.json @@ -20,11 +20,12 @@ "lint": "eslint --ext mjs src test index.mjs index.cjs.mjs README.md", "lint:readme": "remark --no-stdout --frail -u remark-validate-links README.md", "release": "xyz --edit --repo git@github.com:fluture-js/Fluture.git --tag 'X.Y.Z' --script scripts/distribute --increment", - "test": "npm run lint && npm run lint:readme && npm run test:unit && npm run test:prop && npm run test:types && npm run test:build", + "test": "npm run lint && npm run lint:readme && npm run test:unit && npm run test:prop && npm run test:integration && npm run test:types && npm run test:build", + "test:integration": "npm run clean && mocha --require esm --ui bdd --reporter list --full-trace --check-leaks --bail test/integration.js", "test:unit": "npm run clean && mocha --require esm --ui bdd --reporter list --full-trace --check-leaks --bail test/unit.js", "test:prop": "npm run clean && mocha --require esm --ui bdd --reporter list --full-trace --bail test/prop.js", "test:build": "npm run clean && npm run build && es-check es5 index.js && mocha --require esm --ui bdd --reporter dot --bail test/build.js", - "test:coverage": "npm run clean && nyc --extension .mjs --include src mocha --require esm --ui bdd --reporter dot test/unit.js test/prop.js || true", + "test:coverage": "npm run clean && nyc --extension .mjs --include src mocha --require esm --ui bdd --reporter dot test/unit.js test/prop.js test/integration.js || true", "coverage:upload": "nyc report --reporter=text-lcov > coverage.lcov && codecov", "coverage:report": "nyc report --reporter=html", "test:types": "tsc --lib es6 index.d.ts" @@ -71,7 +72,6 @@ "eslint-plugin-markdown": "^1.0.0-beta.7", "esm": "^3.0.81", "fantasy-laws": "^1.0.1", - "fantasy-states": "^0.2.1", "jsverify": "^0.8.3", "mocha": "^6.0.0", "nyc": "^13.0.1", @@ -83,6 +83,7 @@ "rollup-plugin-commonjs": "^9.1.6", "rollup-plugin-node-resolve": "^4.0.0", "sanctuary-benchmark": "^1.0.0", + "sanctuary-either": "^1.1.0", "sanctuary-type-classes": "^11.0.0", "typescript": "^3.0.3", "xyz": "^3.0.0" diff --git a/scripts/test-mem b/scripts/test-mem index 139a1778..f48c3973 100755 --- a/scripts/test-mem +++ b/scripts/test-mem @@ -2,7 +2,7 @@ /* global process setImmediate */ -var Future = require('..'); +var {Future, fork, race, chain, resolve: sync} = require('..'); var {log} = require('util'); var start = Date.now(); @@ -24,8 +24,6 @@ function report(){ } } -var sync = Future.of; - function async(x){ return Future(function(l, r){ setImmediate(r, x) }); } @@ -35,37 +33,37 @@ var cases = Object.create(null); //Should infinitely run until finally running out of memory. cases.syncHeadRecursion = function recur(){ report(); - return sync('l').chain(recur).race(sync('r')); + return race(sync('r'))(chain(recur)(sync('l'))); }; //Should immediately exit with "l". cases.syncDeepRecursion = function recur(){ report(); - return sync('l').race(sync('r').chain(recur)); + return race(chain(recur)(sync('r')))(sync('l')); }; //Should infinitely run without any problems. cases.syncTailRecursion = function recur(){ report(); - return sync('l').race(sync('r')).chain(recur); + return chain(recur)(race(sync('r'))(sync('l'))); }; //Should immediately exit with "r". cases.asyncHeadRecursion = function recur(){ report(); - return async('l').chain(recur).race(async('r')); + return race(async('r'))(chain(recur)(async('l'))); }; //Should immediately exit with "l". cases.asyncDeepRecursion = function recur(){ report(); - return async('l').race(async('r').chain(recur)); + return race(chain(recur)(async('r')))(async('l')); }; //Should infinitely run without any problems. cases.asyncTailRecursion = function recur(){ report(); - return async('l').race(async('r')).chain(recur); + return chain(recur)(race(async('r'))(async('l'))); }; //Expected to run out of memory. @@ -90,10 +88,10 @@ if(typeof f !== 'function'){ log('PID', process.pid); -var cancel = f().fork( - function(e){console.error(e.stack); process.exit(1)}, - function(v){log('resolved', v); process.exit(2)} -); +var cancel = fork + (function(e){console.error(e.stack); process.exit(1)}) + (function(v){log('resolved', v); process.exit(2)}) + (f()); process.once('SIGINT', () => { log('SIGINT caught. Cancelling...'); diff --git a/src/after.mjs b/src/after.mjs index 914bcc38..24df6a40 100644 --- a/src/after.mjs +++ b/src/after.mjs @@ -1,70 +1,23 @@ -import {Future, never} from './future'; -import {show, partial1} from './internal/utils'; -import {isUnsigned} from './internal/predicates'; -import {throwInvalidArgument} from './internal/throw'; -import {captureContext} from './internal/debug'; -import {nil} from './internal/list'; +import {application1, application, any, positiveInteger} from './internal/check'; +import {createInterpreter, never} from './future'; -export function After(time, value){ - this._time = time; - this._value = value; - this.context = captureContext(nil, 'a Future created with after', After); -} - -After.prototype = Object.create(Future.prototype); - -After.prototype._interpret = function After$interpret(rec, rej, res){ - var id = setTimeout(res, this._time, this._value); +export var After = createInterpreter(2, 'after', function After$interpret(rec, rej, res){ + var id = setTimeout(res, this.$1, this.$2); return function After$cancel(){ clearTimeout(id) }; -}; +}); After.prototype.extractRight = function After$extractRight(){ - return [this._value]; -}; - -After.prototype.toString = function After$toString(){ - return 'after(' + show(this._time) + ', ' + show(this._value) + ')'; + return [this.$2]; }; -export function RejectAfter(time, value){ - this._time = time; - this._value = value; - this.context = captureContext(nil, 'a Future created with rejectAfter', After); -} - -RejectAfter.prototype = Object.create(Future.prototype); - -RejectAfter.prototype._interpret = function RejectAfter$interpret(rec, rej){ - var id = setTimeout(rej, this._time, this._value); - return function RejectAfter$cancel(){ clearTimeout(id) }; -}; - -RejectAfter.prototype.extractLeft = function RejectAfter$extractLeft(){ - return [this._value]; -}; - -RejectAfter.prototype.toString = function RejectAfter$toString(){ - return 'rejectAfter(' + show(this._time) + ', ' + show(this._value) + ')'; -}; - -function after$time(time, value){ - return time === Infinity ? never : new After(time, value); -} - -export function after(time, value){ - if(!isUnsigned(time)) throwInvalidArgument('after', 0, 'be a positive Integer', time); - if(arguments.length === 1) return partial1(after$time, time); - return after$time(time, value); -} - -function rejectAfter$time(time, reason){ - return time === Infinity ? never : new RejectAfter(time, reason); +function alwaysNever(_){ + return never; } -export function rejectAfter(time, reason){ - if(!isUnsigned(time)){ - throwInvalidArgument('rejectAfter', 0, 'be a positive Integer', time); - } - if(arguments.length === 1) return partial1(rejectAfter$time, time); - return rejectAfter$time(time, reason); +export function after(time){ + var context1 = application1(after, positiveInteger, time); + return time === Infinity ? alwaysNever : (function after(value){ + var context2 = application(2, after, any, value, context1); + return new After(context2, time, value); + }); } diff --git a/src/alt.mjs b/src/alt.mjs new file mode 100644 index 00000000..4e282c4d --- /dev/null +++ b/src/alt.mjs @@ -0,0 +1,24 @@ +import {application1, application, future, alternative} from './internal/check'; +import {FL} from './internal/const'; +import {createTransformation} from './internal/transformation'; +import {isFuture} from './future'; + +export var AltTransformation = createTransformation(1, 'alt', { + rejected: function AltTransformation$rejected(){ return this.$1 } +}); + +export function alt(left){ + if(isFuture(left)){ + var context1 = application1(alt, future, left); + return function alt(right){ + var context2 = application(2, alt, future, right, context1); + return right._transform(new AltTransformation(context2, left)); + }; + } + + var context = application1(alt, alternative, left); + return function alt(right){ + application(2, alt, alternative, right, context); + return left[FL.alt](right); + }; +} diff --git a/src/and.mjs b/src/and.mjs new file mode 100644 index 00000000..84d0c972 --- /dev/null +++ b/src/and.mjs @@ -0,0 +1,14 @@ +import {application1, application, future} from './internal/check'; +import {createTransformation} from './internal/transformation'; + +export var AndTransformation = createTransformation(1, 'and', { + resolved: function AndTransformation$resolved(){ return this.$1 } +}); + +export function and(left){ + var context1 = application1(and, future, left); + return function and(right){ + var context2 = application(2, and, future, right, context1); + return right._transform(new AndTransformation(context2, left)); + }; +} diff --git a/src/ap.mjs b/src/ap.mjs new file mode 100644 index 00000000..e0ee0d83 --- /dev/null +++ b/src/ap.mjs @@ -0,0 +1,34 @@ +import {application1, application, future, apply as applyArg} from './internal/check'; +import {FL} from './internal/const'; +import {typeError} from './internal/error'; +import {isFunction} from './internal/predicates'; +import {createTransformation} from './internal/transformation'; +import {show} from './internal/utils'; +import {isFuture} from './future'; +import {MapTransformation} from './map'; + +export var ApTransformation = createTransformation(1, 'ap', { + resolved: function ApTransformation$resolved(f){ + if(isFunction(f)) return this.$1._transform(new MapTransformation(this.context, f)); + throw typeError( + 'ap expects the second Future to resolve to a Function\n' + + ' Actual: ' + show(f) + ); + } +}); + +export function ap(mx){ + if(isFuture(mx)){ + var context1 = application1(ap, future, mx); + return function ap(mf){ + var context2 = application(2, ap, future, mf, context1); + return mf._transform(new ApTransformation(context2, mx)); + }; + } + + var context = application1(ap, applyArg, mx); + return function ap(mf){ + application(2, ap, applyArg, mf, context); + return mx[FL.ap](mf); + }; +} diff --git a/src/attempt-p.mjs b/src/attempt-p.mjs new file mode 100644 index 00000000..70d6210c --- /dev/null +++ b/src/attempt-p.mjs @@ -0,0 +1,5 @@ +import {encaseP} from './encase-p'; + +export function attemptP(f){ + return encaseP(f)(undefined); +} diff --git a/src/attempt.mjs b/src/attempt.mjs index e71af28e..487a62fc 100644 --- a/src/attempt.mjs +++ b/src/attempt.mjs @@ -1,29 +1,5 @@ -import {Future} from './future'; -import {noop, showf} from './internal/utils'; -import {isFunction} from './internal/predicates'; -import {throwInvalidArgument} from './internal/throw'; -import {nil} from './internal/list'; -import {captureContext} from './internal/debug'; - -export function Attempt(fn){ - this._fn = fn; - this.context = captureContext(nil, 'a Future created with attempt/try', Attempt); -} - -Attempt.prototype = Object.create(Future.prototype); - -Attempt.prototype._interpret = function Attempt$interpret(rec, rej, res){ - var r; - try{ r = this._fn() }catch(e){ rej(e); return noop } - res(r); - return noop; -}; - -Attempt.prototype.toString = function Attempt$toString(){ - return 'attempt(' + showf(this._fn) + ')'; -}; +import {encase} from './encase'; export function attempt(f){ - if(!isFunction(f)) throwInvalidArgument('attempt', 0, 'be a Function', f); - return new Attempt(f); + return encase(f)(undefined); } diff --git a/src/bimap.mjs b/src/bimap.mjs new file mode 100644 index 00000000..28af77ba --- /dev/null +++ b/src/bimap.mjs @@ -0,0 +1,25 @@ +import {application1, application, func, bifunctor} from './internal/check'; +import {FL} from './internal/const'; +import {createTransformation} from './internal/transformation'; +import {call} from './internal/utils'; +import {isFuture} from './future'; +import {reject} from './reject'; +import {resolve} from './resolve'; + +export var BimapTransformation = createTransformation(2, 'bimap', { + rejected: function BimapTransformation$rejected(x){ return reject(call(this.$1, x)) }, + resolved: function BimapTransformation$resolved(x){ return resolve(call(this.$2, x)) } +}); + +export function bimap(f){ + var context1 = application1(bimap, func, f); + return function bimap(g){ + var context2 = application(2, bimap, func, g, context1); + return function bimap(m){ + var context3 = application(3, bimap, bifunctor, m, context2); + return isFuture(m) ? + m._transform(new BimapTransformation(context3, f, g)) : + m[FL.bimap](f, g); + }; + }; +} diff --git a/src/both.mjs b/src/both.mjs new file mode 100644 index 00000000..195d706f --- /dev/null +++ b/src/both.mjs @@ -0,0 +1,30 @@ +import {application1, application, future} from './internal/check'; +import { + createParallelTransformation, + earlyCrash, + earlyReject +} from './internal/parallel-transformation'; +import {createTransformation} from './internal/transformation'; +import {noop} from './internal/utils'; +import {resolve} from './resolve'; + +export var PairTransformation = createTransformation(1, 'pair', { + resolved: function PairTransformation$resolved(x){ + return resolve([x, this.$1]); + } +}); + +export var BothTransformation = +createParallelTransformation('both', earlyCrash, earlyReject, noop, { + resolved: function BothTransformation$resolved(x){ + return this.$1._transform(new PairTransformation(this.context, x)); + } +}); + +export function both(left){ + var context1 = application1(both, future, left); + return function both(right){ + var context2 = application(2, both, future, right, context1); + return right._transform(new BothTransformation(context2, left)); + }; +} diff --git a/src/cache.mjs b/src/cache.mjs index d5bb026d..f37aa188 100644 --- a/src/cache.mjs +++ b/src/cache.mjs @@ -1,12 +1,12 @@ -import {Future, isFuture} from './future'; +import {application1, future} from './internal/check'; import {noop} from './internal/utils'; -import {throwInvalidFuture} from './internal/throw'; +import {createInterpreter} from './future'; -var Cold = Cached.Cold = 0; -var Pending = Cached.Pending = 1; -var Crashed = Cached.Crashed = 2; -var Rejected = Cached.Rejected = 3; -var Resolved = Cached.Resolved = 4; +export var Cold = 0; +export var Pending = 1; +export var Crashed = 2; +export var Rejected = 3; +export var Resolved = 4; export function Queued(rec, rej, res){ this[Crashed] = rec; @@ -14,28 +14,41 @@ export function Queued(rec, rej, res){ this[Resolved] = res; } -export function Cached(pure){ - this._pure = pure; - this.reset(); -} +export var Cache = createInterpreter(1, 'cache', function Cache$interpret(rec, rej, res){ + var cancel = noop; + + switch(this._state){ + case Pending: cancel = this._addToQueue(rec, rej, res); break; + case Crashed: rec(this._value); break; + case Rejected: rej(this._value); break; + case Resolved: res(this._value); break; + default: cancel = this._addToQueue(rec, rej, res); this.run(); + } -Cached.prototype = Object.create(Future.prototype); + return cancel; +}); -Cached.prototype.extractLeft = function Cached$extractLeft(){ +Cache.prototype._cancel = noop; +Cache.prototype._queue = []; +Cache.prototype._queued = 0; +Cache.prototype._value = undefined; +Cache.prototype._state = Cold; + +Cache.prototype.extractLeft = function Cache$extractLeft(){ return this._state === Rejected ? [this._value] : []; }; -Cached.prototype.extractRight = function Cached$extractRight(){ +Cache.prototype.extractRight = function Cache$extractRight(){ return this._state === Resolved ? [this._value] : []; }; -Cached.prototype._addToQueue = function Cached$addToQueue(rec, rej, res){ +Cache.prototype._addToQueue = function Cache$addToQueue(rec, rej, res){ var _this = this; if(_this._state > Pending) return noop; var i = _this._queue.push(new Queued(rec, rej, res)) - 1; _this._queued = _this._queued + 1; - return function Cached$removeFromQueue(){ + return function Cache$removeFromQueue(){ if(_this._state > Pending) return; _this._queue[i] = undefined; _this._queued = _this._queued - 1; @@ -43,7 +56,7 @@ Cached.prototype._addToQueue = function Cached$addToQueue(rec, rej, res){ }; }; -Cached.prototype._drainQueue = function Cached$drainQueue(){ +Cache.prototype._drainQueue = function Cache$drainQueue(){ if(this._state <= Pending) return; if(this._queued === 0) return; var queue = this._queue; @@ -60,39 +73,39 @@ Cached.prototype._drainQueue = function Cached$drainQueue(){ this._queued = 0; }; -Cached.prototype.crash = function Cached$crash(error){ +Cache.prototype.crash = function Cache$crash(error){ if(this._state > Pending) return; this._value = error; this._state = Crashed; this._drainQueue(); }; -Cached.prototype.reject = function Cached$reject(reason){ +Cache.prototype.reject = function Cache$reject(reason){ if(this._state > Pending) return; this._value = reason; this._state = Rejected; this._drainQueue(); }; -Cached.prototype.resolve = function Cached$resolve(value){ +Cache.prototype.resolve = function Cache$resolve(value){ if(this._state > Pending) return; this._value = value; this._state = Resolved; this._drainQueue(); }; -Cached.prototype.run = function Cached$run(){ +Cache.prototype.run = function Cache$run(){ var _this = this; if(_this._state > Cold) return; _this._state = Pending; - _this._cancel = _this._pure._interpret( - function Cached$fork$rec(x){ _this.crash(x) }, - function Cached$fork$rej(x){ _this.reject(x) }, - function Cached$fork$res(x){ _this.resolve(x) } + _this._cancel = _this.$1._interpret( + function Cache$fork$rec(x){ _this.crash(x) }, + function Cache$fork$rej(x){ _this.reject(x) }, + function Cache$fork$res(x){ _this.resolve(x) } ); }; -Cached.prototype.reset = function Cached$reset(){ +Cache.prototype.reset = function Cache$reset(){ if(this._state === Cold) return; if(this._state === Pending) this._cancel(); this._cancel = noop; @@ -102,25 +115,6 @@ Cached.prototype.reset = function Cached$reset(){ this._state = Cold; }; -Cached.prototype._interpret = function Cached$interpret(rec, rej, res){ - var cancel = noop; - - switch(this._state){ - case Pending: cancel = this._addToQueue(rec, rej, res); break; - case Crashed: rec(this._value); break; - case Rejected: rej(this._value); break; - case Resolved: res(this._value); break; - default: cancel = this._addToQueue(rec, rej, res); this.run(); - } - - return cancel; -}; - -Cached.prototype.toString = function Cached$toString(){ - return 'cache(' + this._pure.toString() + ')'; -}; - export function cache(m){ - if(!isFuture(m)) throwInvalidFuture('cache', 0, m); - return new Cached(m); + return new Cache(application1(cache, future, m), m); } diff --git a/src/chain-rec.mjs b/src/chain-rec.mjs index b4a3dafa..9da75104 100644 --- a/src/chain-rec.mjs +++ b/src/chain-rec.mjs @@ -1,8 +1,14 @@ -import {resolve} from './future'; import {Next, Done} from './internal/iteration'; +import {nil} from './internal/list'; +import {ChainTransformation} from './chain'; +import {resolve} from './resolve'; +//Note: This function is not curried because it's only used to satisfy the +// Fantasy Land ChainRec specification. export function chainRec(step, init){ - return resolve(Next(init))._chain(function chainRec$recur(o){ - return o.done ? resolve(o.value) : step(Next, Done, o.value)._chain(chainRec$recur); - }); + return resolve(Next(init))._transform(new ChainTransformation(nil, function chainRec$recur(o){ + return o.done ? + resolve(o.value) : + step(Next, Done, o.value)._transform(new ChainTransformation(nil, chainRec$recur)); + })); } diff --git a/src/chain-rej.mjs b/src/chain-rej.mjs new file mode 100644 index 00000000..ea2fa0b2 --- /dev/null +++ b/src/chain-rej.mjs @@ -0,0 +1,15 @@ +import {application1, application, future, func} from './internal/check'; +import {createTransformation} from './internal/transformation'; +import {call} from './internal/utils'; + +export var ChainRejTransformation = createTransformation(1, 'chainRej', { + rejected: function ChainRejTransformation$rejected(x){ return call(this.$1, x) } +}); + +export function chainRej(f){ + var context1 = application1(chainRej, func, f); + return function chainRej(m){ + var context2 = application(2, chainRej, future, m, context1); + return m._transform(new ChainRejTransformation(context2, f)); + }; +} diff --git a/src/chain.mjs b/src/chain.mjs new file mode 100644 index 00000000..e05fb127 --- /dev/null +++ b/src/chain.mjs @@ -0,0 +1,19 @@ +import {application1, application, func, monad} from './internal/check'; +import {FL} from './internal/const'; +import {createTransformation} from './internal/transformation'; +import {call} from './internal/utils'; +import {isFuture} from './future'; + +export var ChainTransformation = createTransformation(1, 'chain', { + resolved: function ChainTransformation$resolved(x){ return call(this.$1, x) } +}); + +export function chain(f){ + var context1 = application1(chain, func, f); + return function chain(m){ + var context2 = application(2, chain, monad, m, context1); + return isFuture(m) ? + m._transform(new ChainTransformation(context2, f)) : + m[FL.chain](f); + }; +} diff --git a/src/crash.mjs b/src/crash.mjs new file mode 100644 index 00000000..55ededbf --- /dev/null +++ b/src/crash.mjs @@ -0,0 +1,12 @@ +import {application1, any} from './internal/check'; +import {noop} from './internal/utils'; +import {createInterpreter} from './future'; + +export var Crash = createInterpreter(1, 'crash', function Crash$interpret(rec){ + rec(this.$1); + return noop; +}); + +export function crash(x){ + return new Crash(application1(crash, any, x), x); +} diff --git a/src/dispatchers/alt.mjs b/src/dispatchers/alt.mjs deleted file mode 100644 index 1e08524e..00000000 --- a/src/dispatchers/alt.mjs +++ /dev/null @@ -1,15 +0,0 @@ -import {isAlt} from '../internal/predicates'; -import {FL} from '../internal/const'; -import {partial1} from '../internal/utils'; -import {throwInvalidArgument} from '../internal/throw'; - -function alt$left(left, right){ - if(!isAlt(right)) throwInvalidArgument('alt', 1, 'be an Alt', right); - return left[FL.alt](right); -} - -export function alt(left, right){ - if(!isAlt(left)) throwInvalidArgument('alt', 0, 'be an Alt', left); - if(arguments.length === 1) return partial1(alt$left, left); - return alt$left(left, right); -} diff --git a/src/dispatchers/and.mjs b/src/dispatchers/and.mjs deleted file mode 100644 index f39cbb34..00000000 --- a/src/dispatchers/and.mjs +++ /dev/null @@ -1,14 +0,0 @@ -import {isFuture} from '../future'; -import {partial1} from '../internal/utils'; -import {throwInvalidFuture} from '../internal/throw'; - -function and$left(left, right){ - if(!isFuture(right)) throwInvalidFuture('and', 1, right); - return left.and(right); -} - -export function and(left, right){ - if(!isFuture(left)) throwInvalidFuture('and', 0, left); - if(arguments.length === 1) return partial1(and$left, left); - return and$left(left, right); -} diff --git a/src/dispatchers/ap.mjs b/src/dispatchers/ap.mjs deleted file mode 100644 index fe64d8b6..00000000 --- a/src/dispatchers/ap.mjs +++ /dev/null @@ -1,15 +0,0 @@ -import {isApply} from '../internal/predicates'; -import {FL} from '../internal/const'; -import {partial1} from '../internal/utils'; -import {throwInvalidArgument} from '../internal/throw'; - -function ap$mval(mval, mfunc){ - if(!isApply(mfunc)) throwInvalidArgument('ap', 1, 'be an Apply', mfunc); - return mfunc[FL.ap](mval); -} - -export function ap(mval, mfunc){ - if(!isApply(mval)) throwInvalidArgument('ap', 0, 'be an Apply', mval); - if(arguments.length === 1) return partial1(ap$mval, mval); - return ap$mval(mval, mfunc); -} diff --git a/src/dispatchers/bimap.mjs b/src/dispatchers/bimap.mjs deleted file mode 100644 index 0090f268..00000000 --- a/src/dispatchers/bimap.mjs +++ /dev/null @@ -1,23 +0,0 @@ -import {isBifunctor} from '../internal/predicates'; -import {FL} from '../internal/const'; -import {partial1, partial2} from '../internal/utils'; -import {isFunction} from '../internal/predicates'; -import {throwInvalidArgument} from '../internal/throw'; - -function bimap$lmapper$rmapper(lmapper, rmapper, m){ - if(!isBifunctor(m)) throwInvalidArgument('bimap', 2, 'be a Bifunctor', m); - return m[FL.bimap](lmapper, rmapper); -} - -function bimap$lmapper(lmapper, rmapper, m){ - if(!isFunction(rmapper)) throwInvalidArgument('bimap', 1, 'be a Function', rmapper); - if(arguments.length === 2) return partial2(bimap$lmapper$rmapper, lmapper, rmapper); - return bimap$lmapper$rmapper(lmapper, rmapper, m); -} - -export function bimap(lmapper, rmapper, m){ - if(!isFunction(lmapper)) throwInvalidArgument('bimap', 0, 'be a Function', lmapper); - if(arguments.length === 1) return partial1(bimap$lmapper, lmapper); - if(arguments.length === 2) return bimap$lmapper(lmapper, rmapper); - return bimap$lmapper(lmapper, rmapper, m); -} diff --git a/src/dispatchers/both.mjs b/src/dispatchers/both.mjs deleted file mode 100644 index 5aa101de..00000000 --- a/src/dispatchers/both.mjs +++ /dev/null @@ -1,14 +0,0 @@ -import {isFuture} from '../future'; -import {partial1} from '../internal/utils'; -import {throwInvalidFuture} from '../internal/throw'; - -function both$left(left, right){ - if(!isFuture(right)) throwInvalidFuture('both', 1, right); - return left.both(right); -} - -export function both(left, right){ - if(!isFuture(left)) throwInvalidFuture('both', 0, left); - if(arguments.length === 1) return partial1(both$left, left); - return both$left(left, right); -} diff --git a/src/dispatchers/chain-rej.mjs b/src/dispatchers/chain-rej.mjs deleted file mode 100644 index d20138c3..00000000 --- a/src/dispatchers/chain-rej.mjs +++ /dev/null @@ -1,15 +0,0 @@ -import {isFuture} from '../future'; -import {partial1} from '../internal/utils'; -import {isFunction} from '../internal/predicates'; -import {throwInvalidArgument, throwInvalidFuture} from '../internal/throw'; - -function chainRej$chainer(chainer, m){ - if(!isFuture(m)) throwInvalidFuture('chainRej', 1, m); - return m.chainRej(chainer); -} - -export function chainRej(chainer, m){ - if(!isFunction(chainer)) throwInvalidArgument('chainRej', 0, 'be a Function', chainer); - if(arguments.length === 1) return partial1(chainRej$chainer, chainer); - return chainRej$chainer(chainer, m); -} diff --git a/src/dispatchers/chain.mjs b/src/dispatchers/chain.mjs deleted file mode 100644 index 6e77dbe6..00000000 --- a/src/dispatchers/chain.mjs +++ /dev/null @@ -1,16 +0,0 @@ -import {isChain} from '../internal/predicates'; -import {FL} from '../internal/const'; -import {partial1} from '../internal/utils'; -import {isFunction} from '../internal/predicates'; -import {throwInvalidArgument} from '../internal/throw'; - -function chain$chainer(chainer, m){ - if(!isChain(m)) throwInvalidArgument('chain', 1, 'be a Chain', m); - return m[FL.chain](chainer); -} - -export function chain(chainer, m){ - if(!isFunction(chainer)) throwInvalidArgument('chain', 0, 'be a Function', chainer); - if(arguments.length === 1) return partial1(chain$chainer, chainer); - return chain$chainer(chainer, m); -} diff --git a/src/dispatchers/done.mjs b/src/dispatchers/done.mjs deleted file mode 100644 index 95f6eba3..00000000 --- a/src/dispatchers/done.mjs +++ /dev/null @@ -1,15 +0,0 @@ -import {isFuture} from '../future'; -import {partial1} from '../internal/utils'; -import {isFunction} from '../internal/predicates'; -import {throwInvalidArgument, throwInvalidFuture} from '../internal/throw'; - -function done$callback(callback, m){ - if(!isFuture(m)) throwInvalidFuture('done', 1, m); - return m.done(callback); -} - -export function done(callback, m){ - if(!isFunction(callback)) throwInvalidArgument('done', 0, 'be a Function', callback); - if(arguments.length === 1) return partial1(done$callback, callback); - return done$callback(callback, m); -} diff --git a/src/dispatchers/extract-left.mjs b/src/dispatchers/extract-left.mjs deleted file mode 100644 index abf94c42..00000000 --- a/src/dispatchers/extract-left.mjs +++ /dev/null @@ -1,7 +0,0 @@ -import {isFuture} from '../future'; -import {throwInvalidFuture} from '../internal/throw'; - -export function extractLeft(m){ - if(!isFuture(m)) throwInvalidFuture('extractLeft', 0, m); - return m.extractLeft(); -} diff --git a/src/dispatchers/extract-right.mjs b/src/dispatchers/extract-right.mjs deleted file mode 100644 index d55840ae..00000000 --- a/src/dispatchers/extract-right.mjs +++ /dev/null @@ -1,7 +0,0 @@ -import {isFuture} from '../future'; -import {throwInvalidFuture} from '../internal/throw'; - -export function extractRight(m){ - if(!isFuture(m)) throwInvalidFuture('extractRight', 0, m); - return m.extractRight(); -} diff --git a/src/dispatchers/fold.mjs b/src/dispatchers/fold.mjs deleted file mode 100644 index f8df9182..00000000 --- a/src/dispatchers/fold.mjs +++ /dev/null @@ -1,22 +0,0 @@ -import {isFuture} from '../future'; -import {partial1, partial2} from '../internal/utils'; -import {isFunction} from '../internal/predicates'; -import {throwInvalidArgument, throwInvalidFuture} from '../internal/throw'; - -function fold$f$g(f, g, m){ - if(!isFuture(m)) throwInvalidFuture('fold', 2, m); - return m.fold(f, g); -} - -function fold$f(f, g, m){ - if(!isFunction(g)) throwInvalidArgument('fold', 1, 'be a Function', g); - if(arguments.length === 2) return partial2(fold$f$g, f, g); - return fold$f$g(f, g, m); -} - -export function fold(f, g, m){ - if(!isFunction(f)) throwInvalidArgument('fold', 0, 'be a Function', f); - if(arguments.length === 1) return partial1(fold$f, f); - if(arguments.length === 2) return fold$f(f, g); - return fold$f(f, g, m); -} diff --git a/src/dispatchers/fork-catch.mjs b/src/dispatchers/fork-catch.mjs deleted file mode 100644 index 21e45d4c..00000000 --- a/src/dispatchers/fork-catch.mjs +++ /dev/null @@ -1,15 +0,0 @@ -import {isFuture} from '../future'; -import {partial1, partial2, partial3} from '../internal/utils'; -import {isFunction} from '../internal/predicates'; -import {throwInvalidArgument, throwInvalidFuture} from '../internal/throw'; - -export function forkCatch(f, g, h, m){ - if(!isFunction(f)) throwInvalidArgument('forkCatch', 0, 'be a Function', f); - if(arguments.length === 1) return partial1(forkCatch, f); - if(!isFunction(g)) throwInvalidArgument('forkCatch', 1, 'be a Function', g); - if(arguments.length === 2) return partial2(forkCatch, f, g); - if(!isFunction(h)) throwInvalidArgument('forkCatch', 2, 'be a Function', h); - if(arguments.length === 3) return partial3(forkCatch, f, g, h); - if(!isFuture(m)) throwInvalidFuture('forkCatch', 3, m); - return m._interpret(f, g, h); -} diff --git a/src/dispatchers/fork.mjs b/src/dispatchers/fork.mjs deleted file mode 100644 index eb39bc1d..00000000 --- a/src/dispatchers/fork.mjs +++ /dev/null @@ -1,22 +0,0 @@ -import {isFuture} from '../future'; -import {raise, partial1, partial2} from '../internal/utils'; -import {isFunction} from '../internal/predicates'; -import {throwInvalidArgument, throwInvalidFuture} from '../internal/throw'; - -function fork$f$g(f, g, m){ - if(!isFuture(m)) throwInvalidFuture('fork', 2, m); - return m._interpret(raise, f, g); -} - -function fork$f(f, g, m){ - if(!isFunction(g)) throwInvalidArgument('fork', 1, 'be a Function', g); - if(arguments.length === 2) return partial2(fork$f$g, f, g); - return fork$f$g(f, g, m); -} - -export function fork(f, g, m){ - if(!isFunction(f)) throwInvalidArgument('fork', 0, 'be a Function', f); - if(arguments.length === 1) return partial1(fork$f, f); - if(arguments.length === 2) return fork$f(f, g); - return fork$f(f, g, m); -} diff --git a/src/dispatchers/index.mjs b/src/dispatchers/index.mjs deleted file mode 100644 index 7c16e095..00000000 --- a/src/dispatchers/index.mjs +++ /dev/null @@ -1,25 +0,0 @@ -export {ap} from './ap'; -export {alt, alt as or} from './alt'; -export {map} from './map'; -export {bimap} from './bimap'; -export {chain} from './chain'; - -export {mapRej} from './map-rej'; -export {chainRej} from './chain-rej'; -export {lastly, lastly as finally} from './lastly'; - -export {and} from './and'; -export {both} from './both'; -export {race} from './race'; - -export {swap} from './swap'; -export {fold} from './fold'; - -export {done} from './done'; -export {fork} from './fork'; -export {forkCatch} from './fork-catch'; -export {promise} from './promise'; -export {value} from './value'; - -export {extractLeft} from './extract-left'; -export {extractRight} from './extract-right'; diff --git a/src/dispatchers/lastly.mjs b/src/dispatchers/lastly.mjs deleted file mode 100644 index 0a6775e9..00000000 --- a/src/dispatchers/lastly.mjs +++ /dev/null @@ -1,14 +0,0 @@ -import {isFuture} from '../future'; -import {partial1} from '../internal/utils'; -import {throwInvalidFuture} from '../internal/throw'; - -function lastly$right(right, left){ - if(!isFuture(left)) throwInvalidFuture('lastly', 1, left); - return left.lastly(right); -} - -export function lastly(right, left){ - if(!isFuture(right)) throwInvalidFuture('lastly', 0, right); - if(arguments.length === 1) return partial1(lastly$right, right); - return lastly$right(right, left); -} diff --git a/src/dispatchers/map-rej.mjs b/src/dispatchers/map-rej.mjs deleted file mode 100644 index 8b5bc02b..00000000 --- a/src/dispatchers/map-rej.mjs +++ /dev/null @@ -1,15 +0,0 @@ -import {isFuture} from '../future'; -import {partial1} from '../internal/utils'; -import {isFunction} from '../internal/predicates'; -import {throwInvalidArgument, throwInvalidFuture} from '../internal/throw'; - -function mapRej$mapper(mapper, m){ - if(!isFuture(m)) throwInvalidFuture('mapRej', 1, m); - return m.mapRej(mapper); -} - -export function mapRej(mapper, m){ - if(!isFunction(mapper)) throwInvalidArgument('mapRej', 0, 'be a Function', mapper); - if(arguments.length === 1) return partial1(mapRej$mapper, mapper); - return mapRej$mapper(mapper, m); -} diff --git a/src/dispatchers/map.mjs b/src/dispatchers/map.mjs deleted file mode 100644 index e35a5546..00000000 --- a/src/dispatchers/map.mjs +++ /dev/null @@ -1,16 +0,0 @@ -import {isFunctor} from '../internal/predicates'; -import {FL} from '../internal/const'; -import {partial1} from '../internal/utils'; -import {isFunction} from '../internal/predicates'; -import {throwInvalidArgument} from '../internal/throw'; - -function map$mapper(mapper, m){ - if(!isFunctor(m)) throwInvalidArgument('map', 1, 'be a Functor', m); - return m[FL.map](mapper); -} - -export function map(mapper, m){ - if(!isFunction(mapper)) throwInvalidArgument('map', 0, 'be a Function', mapper); - if(arguments.length === 1) return partial1(map$mapper, mapper); - return map$mapper(mapper, m); -} diff --git a/src/dispatchers/promise.mjs b/src/dispatchers/promise.mjs deleted file mode 100644 index f799b2d3..00000000 --- a/src/dispatchers/promise.mjs +++ /dev/null @@ -1,7 +0,0 @@ -import {isFuture} from '../future'; -import {throwInvalidFuture} from '../internal/throw'; - -export function promise(m){ - if(!isFuture(m)) throwInvalidFuture('promise', 0, m); - return m.promise(); -} diff --git a/src/dispatchers/race.mjs b/src/dispatchers/race.mjs deleted file mode 100644 index 7e65044d..00000000 --- a/src/dispatchers/race.mjs +++ /dev/null @@ -1,14 +0,0 @@ -import {isFuture} from '../future'; -import {partial1} from '../internal/utils'; -import {throwInvalidFuture} from '../internal/throw'; - -function race$right(right, left){ - if(!isFuture(left)) throwInvalidFuture('race', 1, left); - return left.race(right); -} - -export function race(right, left){ - if(!isFuture(right)) throwInvalidFuture('race', 0, right); - if(arguments.length === 1) return partial1(race$right, right); - return race$right(right, left); -} diff --git a/src/dispatchers/swap.mjs b/src/dispatchers/swap.mjs deleted file mode 100644 index 9a46da05..00000000 --- a/src/dispatchers/swap.mjs +++ /dev/null @@ -1,7 +0,0 @@ -import {isFuture} from '../future'; -import {throwInvalidFuture} from '../internal/throw'; - -export function swap(m){ - if(!isFuture(m)) throwInvalidFuture('swap', 0, m); - return m.swap(); -} diff --git a/src/dispatchers/value.mjs b/src/dispatchers/value.mjs deleted file mode 100644 index 2353fc32..00000000 --- a/src/dispatchers/value.mjs +++ /dev/null @@ -1,15 +0,0 @@ -import {isFuture} from '../future'; -import {partial1} from '../internal/utils'; -import {isFunction} from '../internal/predicates'; -import {throwInvalidArgument, throwInvalidFuture} from '../internal/throw'; - -function value$cont(cont, m){ - if(!isFuture(m)) throwInvalidFuture('value', 1, m); - return m.value(cont); -} - -export function value(cont, m){ - if(!isFunction(cont)) throwInvalidArgument('value', 0, 'be a Function', cont); - if(arguments.length === 1) return partial1(value$cont, cont); - return value$cont(cont, m); -} diff --git a/src/done.mjs b/src/done.mjs new file mode 100644 index 00000000..29c851fc --- /dev/null +++ b/src/done.mjs @@ -0,0 +1,13 @@ +import {application1, application, func, future} from './internal/check'; +import {raise} from './internal/utils'; + +export function done(callback){ + var context1 = application1(done, func, callback); + function done$res(x){ + callback(null, x); + } + return function done(m){ + var context2 = application(2, done, future, m, context1); + return m._interpret(raise, callback, done$res, context2); + }; +} diff --git a/src/encase-n.mjs b/src/encase-n.mjs deleted file mode 100644 index f0e66155..00000000 --- a/src/encase-n.mjs +++ /dev/null @@ -1,50 +0,0 @@ -import {Future} from './future'; -import {show, showf, partial1, noop} from './internal/utils'; -import {isFunction} from './internal/predicates'; -import {throwInvalidArgument} from './internal/throw'; -import {makeError} from './internal/error'; -import {nil} from './internal/list'; -import {captureContext} from './internal/debug'; - -export function EncaseN(fn, a){ - this._fn = fn; - this._a = a; - this.context = captureContext(nil, 'a Future created with encaseN', EncaseN); -} - -EncaseN.prototype = Object.create(Future.prototype); - -EncaseN.prototype._interpret = function EncaseN$interpret(rec, rej, res){ - var open = false, cont = function(){ open = true }; - var context = captureContext(this.context, 'consuming an encased Future', EncaseN$interpret); - try{ - this._fn(this._a, function EncaseN$done(err, val){ - cont = err ? function EncaseN3$rej(){ - open = false; - rej(err); - } : function EncaseN3$res(){ - open = false; - res(val); - }; - if(open){ - cont(); - } - }); - }catch(e){ - rec(makeError(e, this, context)); - open = false; - return noop; - } - cont(); - return function EncaseN$cancel(){ open = false }; -}; - -EncaseN.prototype.toString = function EncaseN$toString(){ - return 'encaseN(' + showf(this._fn) + ', ' + show(this._a) + ')'; -}; - -export function encaseN(f, x){ - if(!isFunction(f)) throwInvalidArgument('encaseN', 0, 'be a Function', f); - if(arguments.length === 1) return partial1(encaseN, f); - return new EncaseN(f, x); -} diff --git a/src/encase-n2.mjs b/src/encase-n2.mjs deleted file mode 100644 index 40b8a020..00000000 --- a/src/encase-n2.mjs +++ /dev/null @@ -1,55 +0,0 @@ -import {Future} from './future'; -import {show, showf, partial1, partial2, noop} from './internal/utils'; -import {isFunction} from './internal/predicates'; -import {throwInvalidArgument} from './internal/throw'; -import {makeError} from './internal/error'; -import {nil} from './internal/list'; -import {captureContext} from './internal/debug'; - -export function EncaseN2(fn, a, b){ - this._fn = fn; - this._a = a; - this._b = b; - this.context = captureContext(nil, 'a Future created with encaseN2', EncaseN2); -} - -EncaseN2.prototype = Object.create(Future.prototype); - -EncaseN2.prototype._interpret = function EncaseN2$interpret(rec, rej, res){ - var open = false, cont = function(){ open = true }; - var context = captureContext(this.context, 'consuming an encased Future', EncaseN2$interpret); - try{ - this._fn(this._a, this._b, function EncaseN2$done(err, val){ - cont = err ? function EncaseN2$rej(){ - open = false; - rej(err); - } : function EncaseN2$res(){ - open = false; - res(val); - }; - if(open){ - cont(); - } - }); - }catch(e){ - rec(makeError(e, this, context)); - open = false; - return noop; - } - cont(); - return function EncaseN2$cancel(){ open = false }; -}; - -EncaseN2.prototype.toString = function EncaseN2$toString(){ - return 'encaseN2(' + showf(this._fn) + ', ' + show(this._a) + ', ' + show(this._b) + ')'; -}; - -export function encaseN2(f, x, y){ - if(!isFunction(f)) throwInvalidArgument('encaseN2', 0, 'be a Function', f); - - switch(arguments.length){ - case 1: return partial1(encaseN2, f); - case 2: return partial2(encaseN2, f, x); - default: return new EncaseN2(f, x, y); - } -} diff --git a/src/encase-n3.mjs b/src/encase-n3.mjs deleted file mode 100644 index 9f294424..00000000 --- a/src/encase-n3.mjs +++ /dev/null @@ -1,65 +0,0 @@ -import {Future} from './future'; -import {show, showf, partial1, partial2, partial3, noop} from './internal/utils'; -import {isFunction} from './internal/predicates'; -import {throwInvalidArgument} from './internal/throw'; -import {makeError} from './internal/error'; -import {nil} from './internal/list'; -import {captureContext} from './internal/debug'; - -export function EncaseN3(fn, a, b, c){ - this._fn = fn; - this._a = a; - this._b = b; - this._c = c; - this.context = captureContext(nil, 'a Future created with encaseN3', EncaseN3); -} - -EncaseN3.prototype = Object.create(Future.prototype); - -EncaseN3.prototype._interpret = function EncaseN3$interpret(rec, rej, res){ - var open = false, cont = function(){ open = true }; - var context = captureContext(this.context, 'consuming an encased Future', EncaseN3$interpret); - try{ - this._fn(this._a, this._b, this._c, function EncaseN3$done(err, val){ - cont = err ? function EncaseN3$rej(){ - open = false; - rej(err); - } : function EncaseN3$res(){ - open = false; - res(val); - }; - if(open){ - cont(); - } - }); - }catch(e){ - rec(makeError(e, this, context)); - open = false; - return noop; - } - cont(); - return function EncaseN3$cancel(){ open = false }; -}; - -EncaseN3.prototype.toString = function EncaseN3$toString(){ - return 'encaseN3(' - + showf(this._fn) - + ', ' - + show(this._a) - + ', ' - + show(this._b) - + ', ' - + show(this._c) - + ')'; -}; - -export function encaseN3(f, x, y, z){ - if(!isFunction(f)) throwInvalidArgument('encaseN3', 0, 'be a Function', f); - - switch(arguments.length){ - case 1: return partial1(encaseN3, f); - case 2: return partial2(encaseN3, f, x); - case 3: return partial3(encaseN3, f, x, y); - default: return new EncaseN3(f, x, y, z); - } -} diff --git a/src/encase-p.mjs b/src/encase-p.mjs index ea97c85e..b37bbd49 100644 --- a/src/encase-p.mjs +++ b/src/encase-p.mjs @@ -1,39 +1,29 @@ -import {Future} from './future'; -import {noop, show, showf, partial1} from './internal/utils'; -import {isThenable, isFunction} from './internal/predicates'; -import {typeError} from './internal/error'; -import {throwInvalidArgument} from './internal/throw'; -import {makeError} from './internal/error'; -import {nil} from './internal/list'; +import {application1, application, func, any} from './internal/check'; import {captureContext} from './internal/debug'; +import {makeError, typeError} from './internal/error'; +import {isThenable} from './internal/predicates'; +import {noop, show} from './internal/utils'; +import {createInterpreter} from './future'; function invalidPromise(p, f, a){ return typeError( 'encaseP() expects the function it\'s given to return a Promise/Thenable' - + '\n Actual: ' + (show(p)) + '\n From calling: ' + (showf(f)) - + '\n With: ' + (show(a)) + + '\n Actual: ' + show(p) + '\n From calling: ' + show(f) + + '\n With: ' + show(a) ); } -export function EncaseP(fn, a){ - this._fn = fn; - this._a = a; - this.context = captureContext(nil, 'a Future created with encaseP', EncaseP); -} - -EncaseP.prototype = Object.create(Future.prototype); - -EncaseP.prototype._interpret = function EncaseP$interpret(rec, rej, res){ - var open = true, fn = this._fn, a = this._a, p; +export var EncaseP = createInterpreter(2, 'encaseP', function EncaseP$interpret(rec, rej, res){ + var open = true, fn = this.$1, arg = this.$2, p; var context = captureContext(this.context, 'consuming an encased Future', EncaseP$interpret); try{ - p = fn(a); + p = fn(arg); }catch(e){ rec(makeError(e, this, context)); return noop; } if(!isThenable(p)){ - rec(makeError(invalidPromise(p, fn, a), this, context)); + rec(makeError(invalidPromise(p, fn, arg), this, context)); return noop; } p.then(function EncaseP$res(x){ @@ -48,14 +38,12 @@ EncaseP.prototype._interpret = function EncaseP$interpret(rec, rej, res){ } }); return function EncaseP$cancel(){ open = false }; -}; - -EncaseP.prototype.toString = function EncaseP$toString(){ - return 'encaseP(' + showf(this._fn) + ', ' + show(this._a) + ')'; -}; +}); -export function encaseP(f, x){ - if(!isFunction(f)) throwInvalidArgument('encaseP', 0, 'be a Function', f); - if(arguments.length === 1) return partial1(encaseP, f); - return new EncaseP(f, x); +export function encaseP(f){ + var context1 = application1(encaseP, func, f); + return function encaseP(x){ + var context2 = application(2, encaseP, any, x, context1); + return new EncaseP(context2, f, x); + }; } diff --git a/src/encase-p2.mjs b/src/encase-p2.mjs deleted file mode 100644 index 7d7d5919..00000000 --- a/src/encase-p2.mjs +++ /dev/null @@ -1,67 +0,0 @@ -import {Future} from './future'; -import {noop, show, showf, partial1, partial2} from './internal/utils'; -import {isThenable, isFunction} from './internal/predicates'; -import {typeError} from './internal/error'; -import {throwInvalidArgument} from './internal/throw'; -import {makeError} from './internal/error'; -import {nil} from './internal/list'; -import {captureContext} from './internal/debug'; - -function invalidPromise(p, f, a, b){ - return typeError( - 'encaseP2() expects the function it\'s given to return a Promise/Thenable' - + '\n Actual: ' + (show(p)) + '\n From calling: ' + (showf(f)) - + '\n With 1: ' + (show(a)) - + '\n With 2: ' + (show(b)) - ); -} - -export function EncaseP2(fn, a, b){ - this._fn = fn; - this._a = a; - this._b = b; - this.context = captureContext(nil, 'a Future created with encaseP2', EncaseP2); -} - -EncaseP2.prototype = Object.create(Future.prototype); - -EncaseP2.prototype._interpret = function EncaseP2$interpret(rec, rej, res){ - var open = true, fn = this._fn, a = this._a, b = this._b, p; - var context = captureContext(this.context, 'consuming an encased Future', EncaseP2$interpret); - try{ - p = fn(a, b); - }catch(e){ - rec(makeError(e, this, context)); - return noop; - } - if(!isThenable(p)){ - rec(makeError(invalidPromise(p, fn, a, b), this, context)); - return noop; - } - p.then(function EncaseP2$res(x){ - if(open){ - open = false; - res(x); - } - }, function EncaseP2$rej(x){ - if(open){ - open = false; - rej(x); - } - }); - return function EncaseP2$cancel(){ open = false }; -}; - -EncaseP2.prototype.toString = function EncaseP2$toString(){ - return 'encaseP2(' + showf(this._fn) + ', ' + show(this._a) + ', ' + show(this._b) + ')'; -}; - -export function encaseP2(f, x, y){ - if(!isFunction(f)) throwInvalidArgument('encaseP2', 0, 'be a Function', f); - - switch(arguments.length){ - case 1: return partial1(encaseP2, f); - case 2: return partial2(encaseP2, f, x); - default: return new EncaseP2(f, x, y); - } -} diff --git a/src/encase-p3.mjs b/src/encase-p3.mjs deleted file mode 100644 index 2f065ba4..00000000 --- a/src/encase-p3.mjs +++ /dev/null @@ -1,78 +0,0 @@ -import {Future} from './future'; -import {noop, show, showf, partial1, partial2, partial3} from './internal/utils'; -import {isThenable, isFunction} from './internal/predicates'; -import {typeError} from './internal/error'; -import {throwInvalidArgument} from './internal/throw'; -import {makeError} from './internal/error'; -import {nil} from './internal/list'; -import {captureContext} from './internal/debug'; - -function invalidPromise(p, f, a, b, c){ - return typeError( - 'encaseP3() expects the function it\'s given to return a Promise/Thenable' - + '\n Actual: ' + (show(p)) + '\n From calling: ' + (showf(f)) - + '\n With 1: ' + (show(a)) - + '\n With 2: ' + (show(b)) - + '\n With 3: ' + (show(c)) - ); -} - -export function EncaseP3(fn, a, b, c){ - this._fn = fn; - this._a = a; - this._b = b; - this._c = c; - this.context = captureContext(nil, 'a Future created with encaseP3', EncaseP3); -} - -EncaseP3.prototype = Object.create(Future.prototype); - -EncaseP3.prototype._interpret = function EncaseP3$interpret(rec, rej, res){ - var open = true, fn = this._fn, a = this._a, b = this._b, c = this._c, p; - var context = captureContext(this.context, 'consuming an encased Future', EncaseP3$interpret); - try{ - p = fn(a, b, c); - }catch(e){ - rec(makeError(e, this, context)); - return noop; - } - if(!isThenable(p)){ - rec(makeError(invalidPromise(p, fn, a, b, c), this, context)); - return noop; - } - p.then(function EncaseP3$res(x){ - if(open){ - open = false; - res(x); - } - }, function EncaseP3$rej(x){ - if(open){ - open = false; - rej(x); - } - }); - return function EncaseP3$cancel(){ open = false }; -}; - -EncaseP3.prototype.toString = function EncaseP3$toString(){ - return 'encaseP3(' - + showf(this._fn) - + ', ' - + show(this._a) - + ', ' - + show(this._b) - + ', ' - + show(this._c) - + ')'; -}; - -export function encaseP3(f, x, y, z){ - if(!isFunction(f)) throwInvalidArgument('encaseP3', 0, 'be a Function', f); - - switch(arguments.length){ - case 1: return partial1(encaseP3, f); - case 2: return partial2(encaseP3, f, x); - case 3: return partial3(encaseP3, f, x, y); - default: return new EncaseP3(f, x, y, z); - } -} diff --git a/src/encase.mjs b/src/encase.mjs index a80edc31..3f145c02 100644 --- a/src/encase.mjs +++ b/src/encase.mjs @@ -1,31 +1,18 @@ -import {Future} from './future'; -import {noop, show, showf, partial1} from './internal/utils'; -import {isFunction} from './internal/predicates'; -import {throwInvalidArgument} from './internal/throw'; -import {nil} from './internal/list'; -import {captureContext} from './internal/debug'; +import {application1, application, func, any} from './internal/check'; +import {noop} from './internal/utils'; +import {createInterpreter} from './future'; -export function Encase(fn, a){ - this._fn = fn; - this._a = a; - this.context = captureContext(nil, 'a Future created with encase', Encase); -} - -Encase.prototype = Object.create(Future.prototype); - -Encase.prototype._interpret = function Encase$interpret(rec, rej, res){ - var r; - try{ r = this._fn(this._a) }catch(e){ rej(e); return noop } +export var Encase = createInterpreter(2, 'encase', function Encase$interpret(rec, rej, res){ + var fn = this.$1, r; + try{ r = fn(this.$2) }catch(e){ rej(e); return noop } res(r); return noop; -}; - -Encase.prototype.toString = function Encase$toString(){ - return 'encase(' + showf(this._fn) + ', ' + show(this._a) + ')'; -}; +}); -export function encase(f, x){ - if(!isFunction(f)) throwInvalidArgument('encase', 0, 'be a Function', f); - if(arguments.length === 1) return partial1(encase, f); - return new Encase(f, x); +export function encase(f){ + var context1 = application1(encase, func, f); + return function encase(x){ + var context2 = application(2, encase, any, x, context1); + return new Encase(context2, f, x); + }; } diff --git a/src/encase2.mjs b/src/encase2.mjs deleted file mode 100644 index 9391a836..00000000 --- a/src/encase2.mjs +++ /dev/null @@ -1,36 +0,0 @@ -import {Future} from './future'; -import {noop, show, showf, partial1, partial2} from './internal/utils'; -import {isFunction} from './internal/predicates'; -import {throwInvalidArgument} from './internal/throw'; -import {nil} from './internal/list'; -import {captureContext} from './internal/debug'; - -export function Encase2(fn, a, b){ - this._fn = fn; - this._a = a; - this._b = b; - this.context = captureContext(nil, 'a Future created with encase2', Encase2); -} - -Encase2.prototype = Object.create(Future.prototype); - -Encase2.prototype._interpret = function Encase2$interpret(rec, rej, res){ - var r; - try{ r = this._fn(this._a, this._b) }catch(e){ rej(e); return noop } - res(r); - return noop; -}; - -Encase2.prototype.toString = function Encase2$toString(){ - return 'encase2(' + showf(this._fn) + ', ' + show(this._a) + ', ' + show(this._b) + ')'; -}; - -export function encase2(f, x, y){ - if(!isFunction(f)) throwInvalidArgument('encase2', 0, 'be a Function', f); - - switch(arguments.length){ - case 1: return partial1(encase2, f); - case 2: return partial2(encase2, f, x); - default: return new Encase2(f, x, y); - } -} diff --git a/src/encase3.mjs b/src/encase3.mjs deleted file mode 100644 index d38e5d84..00000000 --- a/src/encase3.mjs +++ /dev/null @@ -1,46 +0,0 @@ -import {Future} from './future'; -import {noop, show, showf, partial1, partial2, partial3} from './internal/utils'; -import {isFunction} from './internal/predicates'; -import {throwInvalidArgument} from './internal/throw'; -import {nil} from './internal/list'; -import {captureContext} from './internal/debug'; - -export function Encase3(fn, a, b, c){ - this._fn = fn; - this._a = a; - this._b = b; - this._c = c; - this.context = captureContext(nil, 'a Future created with encase3', Encase3); -} - -Encase3.prototype = Object.create(Future.prototype); - -Encase3.prototype._interpret = function Encase3$interpret(rec, rej, res){ - var r; - try{ r = this._fn(this._a, this._b, this._c) }catch(e){ rej(e); return noop } - res(r); - return noop; -}; - -Encase3.prototype.toString = function Encase3$toString(){ - return 'encase3(' - + showf(this._fn) - + ', ' - + show(this._a) - + ', ' - + show(this._b) - + ', ' - + show(this._c) - + ')'; -}; - -export function encase3(f, x, y, z){ - if(!isFunction(f)) throwInvalidArgument('encase3', 0, 'be a Function', f); - - switch(arguments.length){ - case 1: return partial1(encase3, f); - case 2: return partial2(encase3, f, x); - case 3: return partial3(encase3, f, x, y); - default: return new Encase3(f, x, y, z); - } -} diff --git a/src/extract-left.mjs b/src/extract-left.mjs new file mode 100644 index 00000000..ce0b0166 --- /dev/null +++ b/src/extract-left.mjs @@ -0,0 +1,6 @@ +import {application1, future} from './internal/check'; + +export function extractLeft(m){ + application1(extractLeft, future, m); + return m.extractLeft(); +} diff --git a/src/extract-right.mjs b/src/extract-right.mjs new file mode 100644 index 00000000..afa36601 --- /dev/null +++ b/src/extract-right.mjs @@ -0,0 +1,6 @@ +import {application1, future} from './internal/check'; + +export function extractRight(m){ + application1(extractRight, future, m); + return m.extractRight(); +} diff --git a/src/fold.mjs b/src/fold.mjs new file mode 100644 index 00000000..3c190335 --- /dev/null +++ b/src/fold.mjs @@ -0,0 +1,20 @@ +import {application1, application, func, future} from './internal/check'; +import {createTransformation} from './internal/transformation'; +import {call} from './internal/utils'; +import {resolve} from './resolve'; + +export var FoldTransformation = createTransformation(2, 'fold', { + rejected: function FoldTransformation$rejected(x){ return resolve(call(this.$1, x)) }, + resolved: function FoldTransformation$resolved(x){ return resolve(call(this.$2, x)) } +}); + +export function fold(f){ + var context1 = application1(fold, func, f); + return function fold(g){ + var context2 = application(2, fold, func, g, context1); + return function fold(m){ + var context3 = application(3, fold, future, m, context2); + return m._transform(new FoldTransformation(context3, f, g)); + }; + }; +} diff --git a/src/fork-catch.mjs b/src/fork-catch.mjs new file mode 100644 index 00000000..767c0367 --- /dev/null +++ b/src/fork-catch.mjs @@ -0,0 +1,15 @@ +import {application, application1, func, future} from './internal/check'; + +export function forkCatch(f){ + var context1 = application1(forkCatch, func, f); + return function forkCatch(g){ + var context2 = application(2, forkCatch, func, g, context1); + return function forkCatch(h){ + var context3 = application(3, forkCatch, func, h, context2); + return function forkCatch(m){ + var context4 = application(4, forkCatch, future, m, context3); + return m._interpret(f, g, h, context4); + }; + }; + }; +} diff --git a/src/fork.mjs b/src/fork.mjs new file mode 100644 index 00000000..e81e4ad3 --- /dev/null +++ b/src/fork.mjs @@ -0,0 +1,13 @@ +import {application, application1, func, future} from './internal/check'; +import {raise} from './internal/utils'; + +export function fork(f){ + var context1 = application1(fork, func, f); + return function fork(g){ + var context2 = application(2, fork, func, g, context1); + return function fork(m){ + var context3 = application(3, fork, future, m, context2); + return m._interpret(raise, f, g, context3); + }; + }; +} diff --git a/src/future.mjs b/src/future.mjs index 4b8e39a3..cff45e39 100644 --- a/src/future.mjs +++ b/src/future.mjs @@ -1,17 +1,16 @@ /*eslint no-cond-assign:0, no-constant-condition:0 */ -import {show, showf, noop, moop, raise} from './internal/utils'; +import {show, noop} from './internal/utils'; import {isFunction} from './internal/predicates'; -import {FL, $$type} from './internal/const'; +import {$$type} from './internal/const'; import {nil, cons, cat, isNil, reverse} from './internal/list'; import type from 'sanctuary-type-identifiers'; -import {error, typeError, invalidFuture, makeError} from './internal/error'; -import {throwInvalidArgument, throwInvalidContext, throwInvalidFuture} from './internal/throw'; +import {typeError, makeError, invalidArgument} from './internal/error'; import {captureContext} from './internal/debug'; export function Future(computation){ - if(!isFunction(computation)) throwInvalidArgument('Future', 0, 'be a Function', computation); - return new Computation(computation); + if(!isFunction(computation)) throw invalidArgument('Future', 0, 'be a Function', computation); + return new Computation(captureContext(nil, 'first application of Future', Future), computation); } export function isFuture(x){ @@ -24,76 +23,11 @@ Future.prototype['@@show'] = function Future$show(){ return this.toString(); }; -Future.prototype[FL.ap] = function Future$FL$ap(other){ - return other._ap(this); -}; - -Future.prototype[FL.map] = function Future$FL$map(mapper){ - return this._map(mapper); -}; - -Future.prototype[FL.bimap] = function Future$FL$bimap(lmapper, rmapper){ - return this._bimap(lmapper, rmapper); -}; - -Future.prototype[FL.chain] = function Future$FL$chain(mapper){ - return this._chain(mapper); -}; - -Future.prototype[FL.alt] = function Future$FL$alt(other){ - return this._alt(other); -}; - Future.prototype.pipe = function Future$pipe(f){ - if(!isFuture(this)) throwInvalidContext('Future#pipe', this); - if(!isFunction(f)) throwInvalidArgument('Future#pipe', 0, 'be a Function', f); + if(!isFunction(f)) throw invalidArgument('Future#pipe', 0, 'be a Function', f); return f(this); }; -Future.prototype.fork = function Future$fork(rej, res){ - if(!isFuture(this)) throwInvalidContext('Future#fork', this); - if(!isFunction(rej)) throwInvalidArgument('Future#fork', 0, 'be a Function', rej); - if(!isFunction(res)) throwInvalidArgument('Future#fork', 1, 'be a Function', res); - return this._interpret(raise, rej, res); -}; - -Future.prototype.forkCatch = function Future$forkCatch(rec, rej, res){ - if(!isFuture(this)) throwInvalidContext('Future#forkCatch', this); - if(!isFunction(rec)) throwInvalidArgument('Future#forkCatch', 0, 'be a Function', rec); - if(!isFunction(rej)) throwInvalidArgument('Future#forkCatch', 1, 'be a Function', rej); - if(!isFunction(res)) throwInvalidArgument('Future#forkCatch', 2, 'be a Function', res); - return this._interpret(rec, rej, res); -}; - -Future.prototype.value = function Future$value(res){ - if(!isFuture(this)) throwInvalidContext('Future#value', this); - if(!isFunction(res)) throwInvalidArgument('Future#value', 0, 'be a Function', res); - var _this = this; - return _this._interpret(raise, function Future$value$rej(x){ - raise(error( - 'Future#value was called on a rejected Future\n' + - ' Rejection: ' + show(x) + '\n' + - ' Future: ' + _this.toString() - )); - }, res); -}; - -Future.prototype.done = function Future$done(callback){ - if(!isFuture(this)) throwInvalidContext('Future#done', this); - if(!isFunction(callback)) throwInvalidArgument('Future#done', 0, 'be a Function', callback); - return this._interpret(raise, - function Future$done$rej(x){ callback(x) }, - function Future$done$res(x){ callback(null, x) }); -}; - -Future.prototype.promise = function Future$promise(){ - if(!isFuture(this)) throwInvalidContext('Future#promise', this); - var _this = this; - return new Promise(function Future$promise$computation(res, rej){ - _this._interpret(raise, rej, res); - }); -}; - Future.prototype.extractLeft = function Future$extractLeft(){ return []; }; @@ -102,24 +36,43 @@ Future.prototype.extractRight = function Future$extractRight(){ return []; }; -Future.prototype._transform = function Future$transform(action){ - return new Transformation(this, cons(action, nil)); +Future.prototype._transform = function Future$transform(transformation){ + return new Transformer(transformation.context, this, cons(transformation, nil)); }; +Future.prototype.isTransformer = false; Future.prototype.context = nil; +Future.prototype.arity = 0; +Future.prototype.name = 'future'; -export function Computation(computation){ - this._computation = computation; - this.context = captureContext(nil, 'a Future created with the Future constructor', Future); -} +Future.prototype.toString = function(){ + var str = this.name; + for(var i = 1; i <= this.arity; i++){ + str += ' (' + show(this['$' + String(i)]) + ')'; + } + return str; +}; + +export function createInterpreter(arity, name, interpret){ + var Interpreter = function(context){ + this.context = context; + for(var i = 1; i <= arity; i++) this['$' + String(i)] = arguments[i]; + }; -Computation.prototype = Object.create(Future.prototype); + Interpreter.prototype = Object.create(Future.prototype); + Interpreter.prototype.arity = arity; + Interpreter.prototype.name = name; + Interpreter.prototype._interpret = interpret; -Computation.prototype._interpret = function Computation$interpret(rec, rej, res){ - var open = false, cancel = noop, cont = function(){ open = true }; + return Interpreter; +} + +export var Computation = +createInterpreter(1, 'Future', function Computation$interpret(rec, rej, res){ + var computation = this.$1, open = false, cancel = noop, cont = function(){ open = true }; var context = captureContext(this.context, 'consuming a Future', Computation$interpret); try{ - cancel = this._computation(function Computation$rej(x){ + cancel = computation(function Computation$rej(x){ cont = function Computation$rej$cont(){ open = false; rej(x); @@ -154,51 +107,36 @@ Computation.prototype._interpret = function Computation$interpret(rec, rej, res) cancel && cancel(); } }; -}; - -Computation.prototype.toString = function Computation$toString(){ - return 'Future(' + showf(this._computation) + ')'; -}; - -export function Transformation(spawn, actions){ - this._spawn = spawn; - this._actions = actions; -} - -Transformation.prototype = Object.create(Future.prototype); - -Transformation.prototype._transform = function Transformation$_transform(action){ - return new Transformation(this._spawn, cons(action, this._actions)); -}; +}); -Transformation.prototype._interpret = function Transformation$interpret(rec, rej, res){ +export var Transformer = createInterpreter(2, '', function Transformer$interpret(rec, rej, res){ - //These are the cold, and hot, action stacks. The cold actions are those that + //These are the cold, and hot, transformation stacks. The cold actions are those that //have yet to run parallel computations, and hot are those that have. var cold = nil, hot = nil; //A linked list of stack traces, tracking context across ticks. - var context = captureContext(nil, 'consuming a transformed Future', Transformation$interpret); + var context = captureContext(nil, 'consuming a transformed Future', Transformer$interpret); - //The context of the last action to run. + //The context of the last transformation to run. var asyncContext = nil; //These combined variables define our current state. // future = the future we are currently forking - // action = the action to be informed when the future settles + // transformation = the transformation to be informed when the future settles // cancel = the cancel function of the current future // settled = a boolean indicating whether a new tick should start // async = a boolean indicating whether we are awaiting a result asynchronously - var future, action, cancel = noop, settled, async = true, it; + var future, transformation, cancel = noop, settled, async = true, it; - //Takes an action from the top of the hot stack and returns it. + //Takes an transformation from the top of the hot stack and returns it. function nextHot(){ var x = hot.head; hot = hot.tail; return x; } - //Takes an action from the top of the cold stack and returns it. + //Takes an transformation from the top of the cold stack and returns it. function nextCold(){ var x = cold.head; cold = cold.tail; @@ -213,35 +151,35 @@ Transformation.prototype._interpret = function Transformation$interpret(rec, rej function settle(m){ settled = true; future = m; - if(future._spawn){ - var tail = future._actions; + if(future.isTransformer){ + var tail = future.$2; while(!isNil(tail)){ cold = cons(tail.head, cold); tail = tail.tail; } - future = future._spawn; + future = future.$1; } if(async) drain(); } //This function serves as a rejection handler for our current future. - //It will tell the current action that the future rejected, and it will - //settle the current tick with the action's answer to that. + //It will tell the current transformation that the future rejected, and it will + //settle the current tick with the transformation's answer to that. function rejected(x){ if(async) context = cat(future.context, cat(asyncContext, context)); - settle(action.rejected(x)); + settle(transformation.rejected(x)); } //This function serves as a resolution handler for our current future. - //It will tell the current action that the future resolved, and it will - //settle the current tick with the action's answer to that. + //It will tell the current transformation that the future resolved, and it will + //settle the current tick with the transformation's answer to that. function resolved(x){ if(async) context = cat(future.context, cat(asyncContext, context)); - settle(action.resolved(x)); + settle(transformation.resolved(x)); } //This function is passed into actions when they are "warmed up". - //If the action decides that it has its result, without the need to await + //If the transformation decides that it has its result, without the need to await //anything else, then it can call this function to force "early termination". //When early termination occurs, all actions which were stacked prior to the //terminator will be skipped. If they were already hot, they will also be @@ -251,17 +189,17 @@ Transformation.prototype._interpret = function Transformation$interpret(rec, rej context = cat(terminator.context, context); cancel(); cold = nil; - if(async && action !== terminator){ - action.cancel(); + if(async && transformation !== terminator){ + transformation.cancel(); while((it = nextHot()) && it !== terminator) it.cancel(); } settle(m); } - //This will cancel the current Future, the current action, and all stacked hot actions. + //This will cancel the current Future, the current transformation, and all stacked hot actions. function Sequence$cancel(){ cancel(); - action && action.cancel(); + transformation && transformation.cancel(); while(it = nextHot()) it.cancel(); } @@ -279,7 +217,7 @@ Transformation.prototype._interpret = function Transformation$interpret(rec, rej //Takes all actions from the cold stack in reverse order, and calls run() on //each of them, passing them the "early" function. If any of them settles (by //calling early()), we abort. After warming up all actions in the cold queue, - //we warm up the current action as well. + //we warm up the current transformation as well. function warmupActions(){ cold = reverse(cold); while(cold !== nil){ @@ -288,7 +226,7 @@ Transformation.prototype._interpret = function Transformation$interpret(rec, rej hot = cons(it, hot); cold = cold.tail; } - action = action.run(early); + transformation = transformation.run(early); } //This function represents our main execution loop. By "tick", we've been @@ -297,11 +235,11 @@ Transformation.prototype._interpret = function Transformation$interpret(rec, rej async = false; while(true){ settled = false; - if(action) asyncContext = action.context; - if(action = nextCold()){ + if(transformation) asyncContext = transformation.context; + if(transformation = nextCold()){ cancel = future._interpret(exception, rejected, resolved); if(!settled) warmupActions(); - }else if(action = nextHot()){ + }else if(transformation = nextHot()){ cancel = future._interpret(exception, rejected, resolved); }else break; if(settled) continue; @@ -317,389 +255,37 @@ Transformation.prototype._interpret = function Transformation$interpret(rec, rej //Return the cancellation function. return Sequence$cancel; +}); + +Transformer.prototype.isTransformer = true; + +Transformer.prototype._transform = function Transformer$_transform(transformation){ + return new Transformer(transformation.context, this.$1, cons(transformation, this.$2)); }; -Transformation.prototype.toString = function Transformation$toString(){ - var str = '', tail = this._actions; +Transformer.prototype.toString = function Transformer$toString(){ + var i, str = this.$1.toString(), str2, tail = this.$2; while(!isNil(tail)){ - str = '.' + tail.head.toString() + str; + str2 = tail.head.name; + for(i = 1; i <= tail.head.arity; i++){ + str2 += ' (' + show(tail.head['$' + String(i)]) + ')'; + } + str = str2 + ' (' + str + ')'; tail = tail.tail; } - return this._spawn.toString() + str; -}; - -export function Crashed(exception){ - this._exception = exception; -} - -Crashed.prototype = Object.create(Future.prototype); - -Crashed.prototype._interpret = function Crashed$interpret(rec){ - rec(this._exception); - return noop; -}; - -Crashed.prototype.toString = function Crashed$toString(){ - return 'Future(function crash(){ throw ' + show(this._exception) + ' })'; -}; - -export function Rejected(value){ - this._value = value; -} - -Rejected.prototype = Object.create(Future.prototype); - -Rejected.prototype._interpret = function Rejected$interpret(rec, rej){ - rej(this._value); - return noop; -}; - -Rejected.prototype.extractLeft = function Rejected$extractLeft(){ - return [this._value]; -}; - -Rejected.prototype.toString = function Rejected$toString(){ - return 'reject(' + show(this._value) + ')'; -}; - -export function reject(x){ - return new Rejected(x); -} - -export function Resolved(value){ - this._value = value; -} - -Resolved.prototype = Object.create(Future.prototype); - -Resolved.prototype._interpret = function Resolved$interpret(rec, rej, res){ - res(this._value); - return noop; -}; - -Resolved.prototype.extractRight = function Resolved$extractRight(){ - return [this._value]; + return str; }; -Resolved.prototype.toString = function Resolved$toString(){ - return 'Future.of(' + show(this._value) + ')'; -}; - -export function resolve(x){ - return new Resolved(x); -} - -function Never(){ - this._isNever = true; -} - -Never.prototype = Object.create(Future.prototype); - -Never.prototype._interpret = function Never$interpret(){ +export var Never = createInterpreter(0, 'never', function Never$interpret(){ return noop; -}; +}); -Never.prototype.toString = function Never$toString(){ - return 'never'; -}; +Never.prototype._isNever = true; -export var never = new Never(); +export var never = new Never(nil); export function isNever(x){ return isFuture(x) && x._isNever === true; } - -function Eager(future){ - var _this = this; - _this.rec = noop; - _this.rej = noop; - _this.res = noop; - _this.crashed = false; - _this.rejected = false; - _this.resolved = false; - _this.value = null; - _this.cancel = future._interpret(function Eager$crash(x){ - _this.value = x; - _this.crashed = true; - _this.cancel = noop; - _this.rec(x); - }, function Eager$reject(x){ - _this.value = x; - _this.rejected = true; - _this.cancel = noop; - _this.rej(x); - }, function Eager$resolve(x){ - _this.value = x; - _this.resolved = true; - _this.cancel = noop; - _this.res(x); - }); -} - -Eager.prototype = Object.create(Future.prototype); - -Eager.prototype._interpret = function Eager$interpret(rec, rej, res){ - if(this.crashed) rec(this.value); - else if(this.rejected) rej(this.value); - else if(this.resolved) res(this.value); - else{ - this.rec = rec; - this.rej = rej; - this.res = res; - } - return this.cancel; -}; - -export var Action = { - rejected: function Action$rejected(x){ this.cancel(); return new Rejected(x) }, - resolved: function Action$resolved(x){ this.cancel(); return new Resolved(x) }, - run: moop, - cancel: noop -}; - -function captureActionContext(name, fn){ - return captureContext(nil, 'a Future transformed with ' + name, fn); -} - -function nullaryActionToString(){ - return this.name + '()'; -} - -function defineNullaryAction(name, prototype){ - var _name = '_' + name; - function NullaryAction(context){ this.context = context } - NullaryAction.prototype = Object.assign(Object.create(Action), prototype); - NullaryAction.prototype.name = name; - NullaryAction.prototype.toString = nullaryActionToString; - Future.prototype[name] = function checkedNullaryTransformation(){ - if(!isFuture(this)) throwInvalidContext('Future#' + name, this); - return this[_name](); - }; - Future.prototype[_name] = function uncheckedNullaryTransformation(){ - return this._transform(new NullaryAction( - captureActionContext(name, uncheckedNullaryTransformation) - )); - }; - return NullaryAction; -} - -function mapperActionToString(){ - return this.name + '(' + showf(this.mapper) + ')'; -} - -function defineMapperAction(name, prototype){ - var _name = '_' + name; - function MapperAction(mapper, context){ this.mapper = mapper; this.context = context } - MapperAction.prototype = Object.assign(Object.create(Action), prototype); - MapperAction.prototype.name = name; - MapperAction.prototype.toString = mapperActionToString; - Future.prototype[name] = function checkedMapperTransformation(mapper){ - if(!isFuture(this)) throwInvalidContext('Future#' + name, this); - if(!isFunction(mapper)) throwInvalidArgument('Future#' + name, 0, 'be a Function', mapper); - return this[_name](mapper); - }; - Future.prototype[_name] = function uncheckedMapperTransformation(mapper){ - return this._transform(new MapperAction( - mapper, - captureActionContext(name, uncheckedMapperTransformation) - )); - }; - return MapperAction; -} - -function bimapperActionToString(){ - return this.name + '(' + showf(this.lmapper) + ', ' + showf(this.rmapper) + ')'; -} - -function defineBimapperAction(name, prototype){ - var _name = '_' + name; - function BimapperAction(lmapper, rmapper, context){ - this.lmapper = lmapper; - this.rmapper = rmapper; - this.context = context; - } - BimapperAction.prototype = Object.assign(Object.create(Action), prototype); - BimapperAction.prototype.name = name; - BimapperAction.prototype.toString = bimapperActionToString; - Future.prototype[name] = function checkedBimapperTransformation(lm, rm){ - if(!isFuture(this)) throwInvalidContext('Future#' + name, this); - if(!isFunction(lm)) throwInvalidArgument('Future#' + name, 0, 'be a Function', lm); - if(!isFunction(rm)) throwInvalidArgument('Future#' + name, 1, 'be a Function', rm); - return this[_name](lm, rm); - }; - Future.prototype[_name] = function uncheckedBimapperTransformation(lmapper, rmapper){ - return this._transform(new BimapperAction( - lmapper, - rmapper, - captureActionContext(name, uncheckedBimapperTransformation) - )); - }; - return BimapperAction; -} - -function otherActionToString(){ - return this.name + '(' + this.other.toString() + ')'; -} - -function defineOtherAction(name, prototype){ - var _name = '_' + name; - function OtherAction(other, context){ this.other = other; this.context = context } - OtherAction.prototype = Object.assign(Object.create(Action), prototype); - OtherAction.prototype.name = name; - OtherAction.prototype.toString = otherActionToString; - Future.prototype[name] = function checkedOtherTransformation(other){ - if(!isFuture(this)) throwInvalidContext('Future#' + name, this); - if(!isFuture(other)) throwInvalidFuture('Future#' + name, 0, other); - return this[_name](other); - }; - Future.prototype[_name] = function uncheckedOtherTransformation(other){ - return this._transform(new OtherAction( - other, - captureActionContext(name, uncheckedOtherTransformation) - )); - }; - return OtherAction; -} - -function defineParallelAction(name, rec, rej, res, prototype){ - var ParallelAction = defineOtherAction(name, prototype); - ParallelAction.prototype.run = function ParallelAction$run(early){ - var eager = new Eager(this.other); - var action = new ParallelAction(eager); - function ParallelAction$early(m){ early(m, action) } - action.context = captureContext( - this.context, - name + ' triggering a parallel Future', - ParallelAction$run - ); - action.cancel = eager._interpret( - function ParallelAction$rec(x){ rec(ParallelAction$early, x) }, - function ParallelAction$rej(x){ rej(ParallelAction$early, x) }, - function ParallelAction$res(x){ res(ParallelAction$early, x) } - ); - return action; - }; - return ParallelAction; -} - -function apActionHandler(f){ - return isFunction(f) ? - this.other._map(function ApAction$resolved$mapper(x){ return f(x) }) : - new Crashed(makeError(typeError( - 'Future#' + this.name + '() expects its first argument to be a Future of a Function\n' + - ' Actual: Future.of(' + show(f) + ')' - ), null, this.context)); -} - -function chainActionHandler(x){ - var m; - try{ m = this.mapper(x) }catch(e){ return new Crashed(makeError(e, null, this.context)) } - return isFuture(m) ? m : new Crashed(makeError(invalidFuture( - 'Future#' + this.name, - 'the function it\'s given to return a Future', - m, - '\n From calling: ' + showf(this.mapper) + '\n With: ' + show(x) - ), null, this.context)); -} - -function returnOther(){ - return this.other; -} - -function mapWith(mapper, create, value, context){ - var m; - try{ m = create(mapper(value)) }catch(e){ m = new Crashed(makeError(e, null, context)) } - return m; -} - -function mapRight(value){ - return mapWith(this.rmapper, resolve, value, this.context); -} - -function earlyCrash(early, x){ - early(new Crashed(x)); -} - -function earlyReject(early, x){ - early(new Rejected(x)); -} - -function earlyResolve(early, x){ - early(new Resolved(x)); -} - -defineOtherAction('ap', { - resolved: apActionHandler -}); - -defineMapperAction('map', { - resolved: function MapAction$resolved(x){ - return mapWith(this.mapper, resolve, x, this.context); - } -}); - -defineBimapperAction('bimap', { - resolved: mapRight, - rejected: function BimapAction$rejected(x){ - return mapWith(this.lmapper, reject, x, this.context); - } -}); - -defineMapperAction('chain', { - resolved: chainActionHandler -}); - -defineMapperAction('mapRej', { - rejected: function MapRejAction$rejected(x){ - return mapWith(this.mapper, reject, x, this.context); - } -}); - -defineMapperAction('chainRej', { - rejected: chainActionHandler -}); - -defineNullaryAction('swap', { - rejected: Action.resolved, - resolved: Action.rejected -}); - -defineBimapperAction('fold', { - resolved: mapRight, - rejected: function FoldAction$rejected(x){ - return mapWith(this.lmapper, resolve, x, this.context); - } -}); - -var finallyAction = { - rejected: function FinallyAction$rejected(x){ return this.other._and(new Rejected(x)) }, - resolved: function FinallyAction$resolved(x){ return this.other._and(new Resolved(x)) } -}; - -defineOtherAction('finally', finallyAction); -defineOtherAction('lastly', finallyAction); - -defineOtherAction('and', { - resolved: returnOther -}); - -var altAction = { - rejected: returnOther -}; - -defineOtherAction('or', altAction); -defineOtherAction('alt', altAction); - -defineParallelAction('_parallelAp', earlyCrash, earlyReject, noop, { - resolved: apActionHandler -}); - -defineParallelAction('race', earlyCrash, earlyReject, earlyResolve, {}); - -defineParallelAction('both', earlyCrash, earlyReject, noop, { - resolved: function BothAction$resolved(x){ - return this.other._map(function BothAction$resolved$mapper(y){ return [x, y] }); - } -}); diff --git a/src/go.mjs b/src/go.mjs index af7905a9..f6efad0b 100644 --- a/src/go.mjs +++ b/src/go.mjs @@ -1,14 +1,14 @@ -/*eslint consistent-return: 0, no-cond-assign: 0*/ +/*eslint consistent-return: 0 */ -import {Future, isFuture} from './future'; -import {isFunction, isIterator} from './internal/predicates'; -import {isIteration} from './internal/iteration'; -import {show, showf, noop} from './internal/utils'; +import {application1, func} from './internal/check'; +import {captureContext} from './internal/debug'; import {typeError, invalidFuture, invalidArgument, makeError} from './internal/error'; -import {throwInvalidArgument} from './internal/throw'; +import {isIteration} from './internal/iteration'; +import {cat} from './internal/list'; +import {isIterator} from './internal/predicates'; import {Undetermined, Synchronous, Asynchronous} from './internal/timing'; -import {nil, cat} from './internal/list'; -import {captureContext} from './internal/debug'; +import {show, noop} from './internal/utils'; +import {createInterpreter, isFuture} from './future'; export function invalidIteration(o){ return typeError( @@ -19,21 +19,12 @@ export function invalidIteration(o){ export function invalidState(x){ return invalidFuture( - 'go', - 'the iterator to produce only valid Futures', - x, + 'go() expects the value produced by the iterator', x, '\n Tip: If you\'re using a generator, make sure you always yield a Future' ); } -export function Go(generator){ - this._generator = generator; - this.context = captureContext(nil, 'a Future created with do-notation', Go); -} - -Go.prototype = Object.create(Future.prototype); - -Go.prototype._interpret = function Go$interpret(rec, rej, res){ +export var Go = createInterpreter(1, 'go', function Go$interpret(rec, rej, res){ var _this = this, timing = Undetermined, cancel = noop, state, value, iterator; @@ -44,7 +35,7 @@ Go.prototype._interpret = function Go$interpret(rec, rej, res){ ); try{ - iterator = _this._generator(); + iterator = _this.$1(); }catch(e){ rec(makeError(e, _this, context)); return noop; @@ -94,13 +85,8 @@ Go.prototype._interpret = function Go$interpret(rec, rej, res){ return function Go$cancel(){ cancel() }; -}; - -Go.prototype.toString = function Go$toString(){ - return 'go(' + showf(this._generator) + ')'; -}; +}); export function go(generator){ - if(!isFunction(generator)) throwInvalidArgument('go', 0, 'be a Function', generator); - return new Go(generator); + return new Go(application1(go, func, generator), generator); } diff --git a/src/hook.mjs b/src/hook.mjs index 33da732b..8a26fccb 100644 --- a/src/hook.mjs +++ b/src/hook.mjs @@ -1,41 +1,26 @@ -import {Future, isFuture} from './future'; -import {noop, show, showf, partial1, partial2, raise} from './internal/utils'; -import {isFunction} from './internal/predicates'; +import {application1, application, func, future} from './internal/check'; +import {noop, show, raise} from './internal/utils'; import {invalidFuture, makeError} from './internal/error'; -import {throwInvalidArgument, throwInvalidFuture} from './internal/throw'; -import {nil} from './internal/list'; import {captureContext} from './internal/debug'; +import {createInterpreter, isFuture} from './future'; function invalidDisposal(m, f, x){ return invalidFuture( - 'hook', - 'the first function it\'s given to return a Future', - m, - '\n From calling: ' + showf(f) + '\n With: ' + show(x) + 'hook() expects the return value from the first function it\'s given', m, + '\n From calling: ' + show(f) + '\n With: ' + show(x) ); } function invalidConsumption(m, f, x){ return invalidFuture( - 'hook', - 'the second function it\'s given to return a Future', - m, - '\n From calling: ' + showf(f) + '\n With: ' + show(x) + 'hook() expects the return value from the second function it\'s given', m, + '\n From calling: ' + show(f) + '\n With: ' + show(x) ); } -export function Hook(acquire, dispose, consume){ - this._acquire = acquire; - this._dispose = dispose; - this._consume = consume; - this.context = captureContext(nil, 'a Future created with hook', Hook); -} - -Hook.prototype = Object.create(Future.prototype); +export var Hook = createInterpreter(3, 'hook', function Hook$interpret(rec, rej, res){ -Hook.prototype._interpret = function Hook$interpret(rec, rej, res){ - - var _this = this, _acquire = this._acquire, _dispose = this._dispose, _consume = this._consume; + var _this = this, _acquire = this.$1, _dispose = this.$2, _consume = this.$3; var cancel, cancelConsume = noop, resource, value, cont = noop; var context = captureContext(_this.context, 'interpreting a hooked Future', Hook$interpret); @@ -128,32 +113,15 @@ Hook.prototype._interpret = function Hook$interpret(rec, rej, res){ cancel(); }; -}; - -Hook.prototype.toString = function Hook$toString(){ - return 'hook(' - + this._acquire.toString() - + ', ' - + showf(this._dispose) - + ', ' - + showf(this._consume) - + ')'; -}; - -function hook$acquire$cleanup(acquire, cleanup, consume){ - if(!isFunction(consume)) throwInvalidArgument('hook', 2, 'be a Function', consume); - return new Hook(acquire, cleanup, consume); -} +}); -function hook$acquire(acquire, cleanup, consume){ - if(!isFunction(cleanup)) throwInvalidArgument('hook', 1, 'be a Function', cleanup); - if(arguments.length === 2) return partial2(hook$acquire$cleanup, acquire, cleanup); - return hook$acquire$cleanup(acquire, cleanup, consume); -} - -export function hook(acquire, cleanup, consume){ - if(!isFuture(acquire)) throwInvalidFuture('hook', 0, acquire); - if(arguments.length === 1) return partial1(hook$acquire, acquire); - if(arguments.length === 2) return hook$acquire(acquire, cleanup); - return hook$acquire(acquire, cleanup, consume); +export function hook(acquire){ + var context1 = application1(hook, future, acquire); + return function hook(dispose){ + var context2 = application(2, hook, func, dispose, context1); + return function hook(consume){ + var context3 = application(3, hook, func, consume, context2); + return new Hook(context3, acquire, dispose, consume); + }; + }; } diff --git a/src/internal/check.mjs b/src/internal/check.mjs new file mode 100644 index 00000000..efed1c66 --- /dev/null +++ b/src/internal/check.mjs @@ -0,0 +1,61 @@ +import {isFuture} from '../future'; + +import {ordinal} from './const'; +import {captureContext} from './debug'; +import {nil} from './list'; +import { + isAlt, + isApply, + isArray, + isBifunctor, + isChain, + isFunction, + isFunctor, + isUnsigned +} from './predicates'; +import {invalidArgument, invalidFutureArgument} from './error'; + +function alwaysTrue(){ + return true; +} + +function invalidArgumentOf(expected){ + return function(it, at, actual){ + return invalidArgument(it, at, expected, actual); + }; +} + +function isFutureArray(xs){ + if(!isArray(xs)) return false; + for(var i = 0; i < xs.length; i++){ + if(!isFuture(xs[i])) return false; + } + return true; +} + +export var alternative = {pred: isAlt, error: invalidArgumentOf('have Alt implemented')}; +export var any = {pred: alwaysTrue, error: invalidArgumentOf('be anything')}; +export var apply = {pred: isApply, error: invalidArgumentOf('have Apply implemented')}; +export var bifunctor = {pred: isBifunctor, error: invalidArgumentOf('have Bifunctor implemented')}; +export var func = {pred: isFunction, error: invalidArgumentOf('be a Function')}; +export var functor = {pred: isFunctor, error: invalidArgumentOf('have Functor implemented')}; +export var future = {pred: isFuture, error: invalidFutureArgument}; +export var monad = {pred: isChain, error: invalidArgumentOf('have Chain implemented')}; +export var positiveInteger = {pred: isUnsigned, error: invalidArgumentOf('be a positive Integer')}; + +export var futureArray = { + pred: isFutureArray, + error: invalidArgumentOf('be an Array of valid Futures') +}; + +export function application(n, f, type, x, prev){ + var context = captureContext(prev, ordinal[n - 1] + ' application of ' + f.name, f); + if(type.pred(x)) return context; + var e = type.error(f.name, n - 1, x); + e.context = context; + throw e; +} + +export function application1(f, type, x){ + return application(1, f, type, x, nil); +} diff --git a/src/internal/const.mjs b/src/internal/const.mjs index b8c9f8f7..4853fa26 100644 --- a/src/internal/const.mjs +++ b/src/internal/const.mjs @@ -13,6 +13,6 @@ export var ordinal = ['first', 'second', 'third', 'fourth', 'fifth']; export var namespace = 'fluture'; export var name = 'Future'; -export var version = 4; +export var version = 5; export var $$type = namespace + '/' + name + '@' + version; diff --git a/src/internal/error.mjs b/src/internal/error.mjs index 82e8898a..2b35c1cb 100644 --- a/src/internal/error.mjs +++ b/src/internal/error.mjs @@ -45,19 +45,22 @@ function invalidVersion(m, x){ ); } -export function invalidFuture(it, at, m, s){ +export function invalidFuture(desc, m, s){ var id = type.parse(type(m)); var info = id.name === name ? '\n' + ( id.namespace !== namespace ? invalidNamespace(m, id.namespace) : id.version !== version ? invalidVersion(m, id.version) : 'Nothing seems wrong. Contact the Fluture maintainers.') : ''; return typeError( - it + '() expects ' + - (ordinal[at] ? 'its ' + ordinal[at] + ' argument to be a valid Future' : at) + - '.' + info + '\n Actual: ' + show(m) + ' :: ' + id.name + (s || '') + desc + ' to be a valid Future.' + info + '\n' + + ' Actual: ' + show(m) + ' :: ' + id.name + (s || '') ); } +export function invalidFutureArgument(it, at, m, s){ + return invalidFuture(it + '() expects its ' + ordinal[at] + ' argument', m, s); +} + export function ensureError(value, fn){ var message; try{ diff --git a/src/internal/parallel-transformation.mjs b/src/internal/parallel-transformation.mjs new file mode 100644 index 00000000..860e2981 --- /dev/null +++ b/src/internal/parallel-transformation.mjs @@ -0,0 +1,81 @@ +import {captureContext} from './debug'; +import {createTransformation} from './transformation'; +import {noop} from './utils'; +import {Future} from '../future'; +import {crash} from '../crash'; +import {reject} from '../reject'; +import {resolve} from '../resolve'; + +function Eager(future){ + var _this = this; + _this.rec = noop; + _this.rej = noop; + _this.res = noop; + _this.crashed = false; + _this.rejected = false; + _this.resolved = false; + _this.value = null; + _this.cancel = future._interpret(function Eager$crash(x){ + _this.value = x; + _this.crashed = true; + _this.cancel = noop; + _this.rec(x); + }, function Eager$reject(x){ + _this.value = x; + _this.rejected = true; + _this.cancel = noop; + _this.rej(x); + }, function Eager$resolve(x){ + _this.value = x; + _this.resolved = true; + _this.cancel = noop; + _this.res(x); + }); +} + +Eager.prototype = Object.create(Future.prototype); + +Eager.prototype._interpret = function Eager$interpret(rec, rej, res){ + if(this.crashed) rec(this.value); + else if(this.rejected) rej(this.value); + else if(this.resolved) res(this.value); + else{ + this.rec = rec; + this.rej = rej; + this.res = res; + } + return this.cancel; +}; + +export function earlyCrash(early, x){ + early(crash(x)); +} + +export function earlyReject(early, x){ + early(reject(x)); +} + +export function earlyResolve(early, x){ + early(resolve(x)); +} + +export function createParallelTransformation(name, rec, rej, res, prototype){ + var ParallelTransformation = createTransformation(1, name, Object.assign({ + run: function Parallel$run(early){ + var eager = new Eager(this.$1); + var transformation = new ParallelTransformation(captureContext( + this.context, + name + ' triggering a parallel Future', + Parallel$run + ), eager); + function Parallel$early(m){ early(m, transformation) } + transformation.cancel = eager._interpret( + function Parallel$rec(x){ rec(Parallel$early, x) }, + function Parallel$rej(x){ rej(Parallel$early, x) }, + function Parallel$res(x){ res(Parallel$early, x) } + ); + return transformation; + } + }, prototype)); + return ParallelTransformation; +} diff --git a/src/internal/throw.mjs b/src/internal/throw.mjs deleted file mode 100644 index f4888f2c..00000000 --- a/src/internal/throw.mjs +++ /dev/null @@ -1,13 +0,0 @@ -import {invalidArgument, invalidContext, invalidFuture} from './error'; - -export function throwInvalidArgument(it, at, expected, actual){ - throw invalidArgument(it, at, expected, actual); -} - -export function throwInvalidContext(it, actual){ - throw invalidContext(it, actual); -} - -export function throwInvalidFuture(it, at, m, s){ - throw invalidFuture(it, at, m, s); -} diff --git a/src/internal/transformation.mjs b/src/internal/transformation.mjs new file mode 100644 index 00000000..ee075d4a --- /dev/null +++ b/src/internal/transformation.mjs @@ -0,0 +1,71 @@ +import {invalidFuture} from './error'; +import {nil} from './list'; +import {noop, moop, show} from './utils'; + +import {crash} from '../crash'; +import {isFuture} from '../future'; +import {reject} from '../reject'; +import {resolve} from '../resolve'; + +function BaseTransformation$rejected(x){ + this.cancel(); + return reject(x); +} + +function BaseTransformation$resolved(x){ + this.cancel(); + return resolve(x); +} + +export var BaseTransformation = { + rejected: BaseTransformation$rejected, + resolved: BaseTransformation$resolved, + run: moop, + cancel: noop, + context: nil, + arity: 0, + name: 'transform' +}; + +function wrapHandler(handler){ + return function transformationHandler(x){ + var m; + try{ + m = handler.call(this, x); + }catch(e){ + return crash(e); + } + if(isFuture(m)){ + return m; + } + return crash(invalidFuture( + this.name + ' expects the return value from the function it\'s given', m, + '\n When called with: ' + show(x) + )); + }; +} + +export function createTransformation(arity, name, prototype){ + var Transformation = function(context){ + this.context = context; + for(var i = 1; i <= arity; i++) this['$' + String(i)] = arguments[i]; + }; + + Transformation.prototype = Object.create(BaseTransformation); + Transformation.prototype.arity = arity; + Transformation.prototype.name = name; + + if(typeof prototype.rejected === 'function'){ + Transformation.prototype.rejected = wrapHandler(prototype.rejected); + } + + if(typeof prototype.resolved === 'function'){ + Transformation.prototype.resolved = wrapHandler(prototype.resolved); + } + + if(typeof prototype.run === 'function'){ + Transformation.prototype.run = prototype.run; + } + + return Transformation; +} diff --git a/src/internal/utils.mjs b/src/internal/utils.mjs index bf6813c1..480e32a5 100644 --- a/src/internal/utils.mjs +++ b/src/internal/utils.mjs @@ -1,35 +1,11 @@ -import show from 'sanctuary-show'; +export {default as show} from 'sanctuary-show'; /* istanbul ignore next: non v8 compatibility */ var setImmediate = typeof setImmediate === 'undefined' ? setImmediateFallback : setImmediate; -export {show}; export function noop(){} export function moop(){ return this } -export function padf(sf, s){ return s.replace(/^/gm, sf).replace(sf, '') } -export function showf(f){ return padf(' ', show(f)) } - -export function partial1(f, a){ - return function bound1(b, c, d){ - switch(arguments.length){ - case 1: return f(a, b); - case 2: return f(a, b, c); - default: return f(a, b, c, d); - } - }; -} - -export function partial2(f, a, b){ - return function bound2(c, d){ - return arguments.length === 1 ? f(a, b, c) : f(a, b, c, d); - }; -} - -export function partial3(f, a, b, c){ - return function bound3(d){ - return f(a, b, c, d); - }; -} +export function call(f, x){ return f(x) } export function setImmediateFallback(f, x){ return setTimeout(f, 0, x); diff --git a/src/lastly.mjs b/src/lastly.mjs new file mode 100644 index 00000000..90c47c69 --- /dev/null +++ b/src/lastly.mjs @@ -0,0 +1,22 @@ +import {application1, application, future} from './internal/check'; +import {createTransformation} from './internal/transformation'; +import {AndTransformation} from './and'; +import {reject} from './reject'; +import {resolve} from './resolve'; + +export var LastlyTransformation = createTransformation(1, 'lastly', { + rejected: function FinallyAction$rejected(x){ + return this.$1._transform(new AndTransformation(this.context, reject(x))); + }, + resolved: function FinallyAction$resolved(x){ + return this.$1._transform(new AndTransformation(this.context, resolve(x))); + } +}); + +export function lastly(cleanup){ + var context1 = application1(lastly, future, cleanup); + return function lastly(program){ + var context2 = application(2, lastly, future, program, context1); + return program._transform(new LastlyTransformation(context2, cleanup)); + }; +} diff --git a/src/map-rej.mjs b/src/map-rej.mjs new file mode 100644 index 00000000..3970b2be --- /dev/null +++ b/src/map-rej.mjs @@ -0,0 +1,16 @@ +import {application1, application, future, func} from './internal/check'; +import {createTransformation} from './internal/transformation'; +import {call} from './internal/utils'; +import {reject} from './reject'; + +export var MapRejTransformation = createTransformation(1, 'mapRej', { + rejected: function MapRejTransformation$rejected(x){ return reject(call(this.$1, x)) } +}); + +export function mapRej(f){ + var context1 = application1(mapRej, func, f); + return function mapRej(m){ + var context2 = application(2, mapRej, future, m, context1); + return m._transform(new MapRejTransformation(context2, f)); + }; +} diff --git a/src/map.mjs b/src/map.mjs new file mode 100644 index 00000000..f133d9d6 --- /dev/null +++ b/src/map.mjs @@ -0,0 +1,20 @@ +import {application1, application, func, functor} from './internal/check'; +import {FL} from './internal/const'; +import {createTransformation} from './internal/transformation'; +import {call} from './internal/utils'; +import {isFuture} from './future'; +import {resolve} from './resolve'; + +export var MapTransformation = createTransformation(1, 'map', { + resolved: function MapTransformation$resolved(x){ return resolve(call(this.$1, x)) } +}); + +export function map(f){ + var context1 = application1(map, func, f); + return function map(m){ + var context2 = application(2, map, functor, m, context1); + return isFuture(m) ? + m._transform(new MapTransformation(context2, f)) : + m[FL.map](f); + }; +} diff --git a/src/node.mjs b/src/node.mjs index c0f716d7..e0dcbf9c 100644 --- a/src/node.mjs +++ b/src/node.mjs @@ -1,47 +1,34 @@ -import {Future} from './future'; -import {showf, noop} from './internal/utils'; -import {isFunction} from './internal/predicates'; -import {throwInvalidArgument} from './internal/throw'; -import {makeError} from './internal/error'; -import {nil} from './internal/list'; -import {captureContext} from './internal/debug'; +import {application1, func} from './internal/check'; +import {wrapException} from './internal/error'; +import {noop, call} from './internal/utils'; +import {createInterpreter} from './future'; -export function Node(fn){ - this._fn = fn; - this.context = captureContext(nil, 'a Future created with node'); -} - -Node.prototype = Object.create(Future.prototype); - -Node.prototype._interpret = function Node$interpret(rec, rej, res){ +export var Node = createInterpreter(1, 'node', function Node$interpret(rec, rej, res){ + function Node$done(err, val){ + cont = err ? function EncaseN3$rej(){ + open = false; + rej(err); + } : function EncaseN3$res(){ + open = false; + res(val); + }; + if(open){ + cont(); + } + } var open = false, cont = function(){ open = true }; try{ - this._fn(function Node$done(err, val){ - cont = err ? function Node$rej(){ - open = false; - rej(err); - } : function Node$res(){ - open = false; - res(val); - }; - if(open){ - cont(); - } - }); + call(this.$1, Node$done); }catch(e){ - rec(makeError(e, this, this.context)); + rec(wrapException(e, this)); open = false; return noop; } cont(); return function Node$cancel(){ open = false }; -}; - -Node.prototype.toString = function Node$toString(){ - return 'node(' + showf(this._fn) + ')'; -}; +}); export function node(f){ - if(!isFunction(f)) throwInvalidArgument('node', 0, 'be a Function', f); - return new Node(f); + var context = application1(node, func, f); + return new Node(context, f); } diff --git a/src/parallel-ap.mjs b/src/parallel-ap.mjs new file mode 100644 index 00000000..a69a5775 --- /dev/null +++ b/src/parallel-ap.mjs @@ -0,0 +1,30 @@ +import {application1, application, future} from './internal/check'; +import { + createParallelTransformation, + earlyCrash, + earlyReject +} from './internal/parallel-transformation'; +import {noop} from './internal/utils'; +import {typeError} from './internal/error'; +import {isFunction} from './internal/predicates'; +import {show} from './internal/utils'; +import {MapTransformation} from './map'; + +export var ParallelApTransformation = +createParallelTransformation('parallelAp', earlyCrash, earlyReject, noop, { + resolved: function ParallelApTransformation$resolved(f){ + if(isFunction(f)) return this.$1._transform(new MapTransformation(this.context, f)); + throw typeError( + 'parallelAp expects the second Future to resolve to a Function\n' + + ' Actual: ' + show(f) + ); + } +}); + +export function parallelAp(mx){ + var context1 = application1(parallelAp, future, mx); + return function parallelAp(mf){ + var context2 = application(2, parallelAp, future, mf, context1); + return mf._transform(new ParallelApTransformation(context2, mx)); + }; +} diff --git a/src/parallel.mjs b/src/parallel.mjs index a843c7da..55c7d521 100644 --- a/src/parallel.mjs +++ b/src/parallel.mjs @@ -1,23 +1,13 @@ -import {Future, Resolved, isFuture} from './future'; -import {throwInvalidFuture, throwInvalidArgument} from './internal/throw'; -import {nil} from './internal/list'; -import {noop, show, partial1} from './internal/utils'; -import {isUnsigned, isArray} from './internal/predicates'; -import {makeError} from './internal/error'; +import {application1, positiveInteger, application, futureArray} from './internal/check'; import {captureContext} from './internal/debug'; +import {makeError} from './internal/error'; +import {noop} from './internal/utils'; +import {createInterpreter} from './future'; +import {resolve} from './resolve'; -export function Parallel(max, futures){ - this._futures = futures; - this._length = futures.length; - this._max = Math.min(this._length, max); - this.context = captureContext(nil, 'a Future created with parallel', Parallel); -} - -Parallel.prototype = Object.create(Future.prototype); - -Parallel.prototype._interpret = function Parallel$interpret(rec, rej, res){ +export var Parallel = createInterpreter(2, 'parallel', function Parallel$interpret(rec, rej, res){ - var _futures = this._futures, _length = this._length, _max = this._max; + var _futures = this.$2, _length = _futures.length, _max = Math.min(_length, this.$1); var cancels = new Array(_length), out = new Array(_length); var cursor = 0, running = 0, blocked = false; var context = captureContext(this.context, 'consuming a parallel Future', Parallel$interpret); @@ -56,30 +46,14 @@ Parallel.prototype._interpret = function Parallel$interpret(rec, rej, res){ return Parallel$cancel; -}; +}); -Parallel.prototype.toString = function Parallel$toString(){ - return 'parallel(' + this._max + ', ' + show(this._futures) + ')'; -}; - -var emptyArray = new Resolved([]); - -function validateNthFuture(m, xs){ - if(!isFuture(m)) throwInvalidFuture( - 'parallel', - 'its second argument to be an Array of valid Futures', - xs - ); -} - -function parallel$max(max, xs){ - if(!isArray(xs)) throwInvalidArgument('parallel', 1, 'be an Array of valid Futures', xs); - for(var idx = 0; idx < xs.length; idx++) validateNthFuture(xs[idx], xs); - return xs.length === 0 ? emptyArray : new Parallel(max, xs); -} +var emptyArray = resolve([]); -export function parallel(max, xs){ - if(!isUnsigned(max)) throwInvalidArgument('parallel', 0, 'be a positive Integer', max); - if(arguments.length === 1) return partial1(parallel$max, max); - return parallel$max(max, xs); +export function parallel(max){ + var context1 = application1(parallel, positiveInteger, max); + return function parallel(ms){ + var context2 = application(2, parallel, futureArray, ms, context1); + return ms.length === 0 ? emptyArray : new Parallel(context2, max, ms); + }; } diff --git a/src/promise.mjs b/src/promise.mjs new file mode 100644 index 00000000..cee2fe1b --- /dev/null +++ b/src/promise.mjs @@ -0,0 +1,9 @@ +import {application1, future} from './internal/check'; +import {raise} from './internal/utils'; + +export function promise(m){ + var context1 = application1(promise, future, m); + return new Promise(function promise$computation(res, rej){ + m._interpret(raise, rej, res, context1); + }); +} diff --git a/src/race.mjs b/src/race.mjs new file mode 100644 index 00000000..63045eff --- /dev/null +++ b/src/race.mjs @@ -0,0 +1,18 @@ +import {application1, application, future} from './internal/check'; +import { + createParallelTransformation, + earlyCrash, + earlyReject, + earlyResolve +} from './internal/parallel-transformation'; + +export var RaceTransformation = +createParallelTransformation('race', earlyCrash, earlyReject, earlyResolve, {}); + +export function race(left){ + var context1 = application1(race, future, left); + return function race(right){ + var context2 = application(2, race, future, right, context1); + return right._transform(new RaceTransformation(context2, left)); + }; +} diff --git a/src/reject-after.mjs b/src/reject-after.mjs new file mode 100644 index 00000000..31d1d036 --- /dev/null +++ b/src/reject-after.mjs @@ -0,0 +1,24 @@ +import {application1, application, any, positiveInteger} from './internal/check'; +import {createInterpreter, never} from './future'; + +export var RejectAfter = +createInterpreter(2, 'rejectAfter', function RejectAfter$interpret(rec, rej){ + var id = setTimeout(rej, this.$1, this.$2); + return function RejectAfter$cancel(){ clearTimeout(id) }; +}); + +RejectAfter.prototype.extractLeft = function RejectAfter$extractLeft(){ + return [this.$2]; +}; + +function alwaysNever(_){ + return never; +} + +export function rejectAfter(time){ + var context1 = application1(rejectAfter, positiveInteger, time); + return time === Infinity ? alwaysNever : (function rejectAfter(value){ + var context2 = application(2, rejectAfter, any, value, context1); + return new RejectAfter(context2, time, value); + }); +} diff --git a/src/reject.mjs b/src/reject.mjs new file mode 100644 index 00000000..2a06ddb3 --- /dev/null +++ b/src/reject.mjs @@ -0,0 +1,16 @@ +import {application1, any} from './internal/check'; +import {noop} from './internal/utils'; +import {createInterpreter} from './future'; + +export var Reject = createInterpreter(1, 'reject', function Reject$interpret(rec, rej){ + rej(this.$1); + return noop; +}); + +Reject.prototype.extractLeft = function Reject$extractLeft(){ + return [this.$1]; +}; + +export function reject(x){ + return new Reject(application1(reject, any, x), x); +} diff --git a/src/resolve.mjs b/src/resolve.mjs new file mode 100644 index 00000000..5960955e --- /dev/null +++ b/src/resolve.mjs @@ -0,0 +1,16 @@ +import {application1, any} from './internal/check'; +import {noop} from './internal/utils'; +import {createInterpreter} from './future'; + +export var Resolve = createInterpreter(1, 'resolve', function Resolve$interpret(rec, rej, res){ + res(this.$1); + return noop; +}); + +Resolve.prototype.extractRight = function Resolve$extractRight(){ + return [this.$1]; +}; + +export function resolve(x){ + return new Resolve(application1(resolve, any, x), x); +} diff --git a/src/swap.mjs b/src/swap.mjs new file mode 100644 index 00000000..f807b86b --- /dev/null +++ b/src/swap.mjs @@ -0,0 +1,14 @@ +import {application1, future} from './internal/check'; +import {createTransformation} from './internal/transformation'; +import {reject} from './reject'; +import {resolve} from './resolve'; + +export var SwapTransformation = createTransformation(0, 'swap', { + resolved: reject, + rejected: resolve +}); + +export function swap(m){ + var context = application1(swap, future, m); + return m._transform(new SwapTransformation(context)); +} diff --git a/src/try-p.mjs b/src/try-p.mjs deleted file mode 100644 index 4a0d9b0f..00000000 --- a/src/try-p.mjs +++ /dev/null @@ -1,57 +0,0 @@ -import {Future} from './future'; -import {noop, show, showf} from './internal/utils'; -import {isThenable, isFunction} from './internal/predicates'; -import {typeError} from './internal/error'; -import {throwInvalidArgument} from './internal/throw'; -import {makeError} from './internal/error'; -import {nil} from './internal/list'; -import {captureContext} from './internal/debug'; - -function invalidPromise(p, f){ - return typeError( - 'tryP() expects the function it\'s given to return a Promise/Thenable' - + '\n Actual: ' + show(p) + '\n From calling: ' + showf(f) - ); -} - -export function TryP(fn){ - this._fn = fn; - this.context = captureContext(nil, 'a Future created with tryP', TryP); -} - -TryP.prototype = Object.create(Future.prototype); - -TryP.prototype._interpret = function TryP$interpret(rec, rej, res){ - var open = true, fn = this._fn, p; - try{ - p = fn(); - }catch(e){ - rec(makeError(e, this, this.context)); - return noop; - } - if(!isThenable(p)){ - rec(makeError(invalidPromise(p, fn), this, this.context)); - return noop; - } - p.then(function TryP$res(x){ - if(open){ - open = false; - res(x); - } - }, function TryP$rej(x){ - if(open){ - open = false; - rej(x); - } - }); - return function TryP$cancel(){ open = false }; -}; - -TryP.prototype.toString = function TryP$toString(){ - return 'tryP(' + show(this._fn) + ')'; -}; - -export function tryP(f){ - if(!isFunction(f)) throwInvalidArgument('tryP', 0, 'be a Function', f); - return new TryP(f); -} diff --git a/src/value.mjs b/src/value.mjs new file mode 100644 index 00000000..1cea491f --- /dev/null +++ b/src/value.mjs @@ -0,0 +1,18 @@ +import {application1, application, func, future} from './internal/check'; +import {error} from './internal/error'; +import {raise, show} from './internal/utils'; + +export function value(res){ + var context1 = application1(value, func, res); + return function value(m){ + var context2 = application(2, value, future, m, context1); + function value$rej(x){ + raise(error( + 'Future#value was called on a rejected Future\n' + + ' Rejection: ' + show(x) + '\n' + + ' Future: ' + show(m) + )); + } + return m._interpret(raise, value$rej, res, context2); + }; +} diff --git a/test/integration.js b/test/integration.js new file mode 100644 index 00000000..773c8491 --- /dev/null +++ b/test/integration.js @@ -0,0 +1 @@ +import './integration/main.mjs'; diff --git a/test/integration/main.mjs b/test/integration/main.mjs new file mode 100644 index 00000000..6210bb45 --- /dev/null +++ b/test/integration/main.mjs @@ -0,0 +1,98 @@ +import {Future, resolve, after, chain, race, map} from '../../index.mjs'; +import {noop, error, assertResolved, eq} from '../util/util'; +import {resolved, resolvedSlow} from '../util/futures'; + +function through (x, fs){ + return fs.reduce(function (y, f){ + return f(y); + }, x); +} + +describe('Future in general', function (){ + + it('is capable of joining', function (){ + var m = through(resolve('a'), [ + chain(function (x){ return chain(function (x){ return after(5)(x + 'c') })(after(5)(x + 'b')) }), + chain(function (x){ return after(5)(x + 'd') }), + chain(function (x){ return resolve(x + 'e') }), + chain(function (x){ return after(5)(x + 'f') }), + ]); + return assertResolved(m, 'abcdef'); + }); + + it('is capable of early termination', function (done){ + var slow = Future(function (){ + var id = setTimeout(done, 20, new Error('Not terminated')); + return function (){ return clearTimeout(id) }; + }); + var m = through(slow, [race(slow), race(slow), race(slow), race(resolved)]); + m._interpret(done, noop, noop); + setTimeout(done, 40, null); + }); + + it('cancels running actions when one early-terminates asynchronously', function (done){ + var slow = Future(function (){ + var id = setTimeout(done, 50, new Error('Not terminated')); + return function (){ return clearTimeout(id) }; + }); + var m = through(slow, [race(slow), race(slow), race(slow), race(resolvedSlow)]); + m._interpret(done, noop, noop); + setTimeout(done, 100, null); + }); + + it('does not run actions unnecessarily when one early-terminates synchronously', function (done){ + var broken = Future(function (){ done(error) }); + var m = through(resolvedSlow, [race(broken), race(broken), race(resolved)]); + m._interpret(done, noop, function (){ return done() }); + }); + + it('resolves the left-hand side first when running actions in parallel', function (){ + var m = through(resolve(1), [ + map(function (x){ return x }), + chain(function (x){ return resolve(x) }), + race(resolve(2)), + ]); + return assertResolved(m, 1); + }); + + it('does not forget about actions to run after early termination', function (){ + var m = through('a', [ + after(30), + race(after(20)('b')), + map(function (x){ return (x + 'c') }), + ]); + return assertResolved(m, 'bc'); + }); + + it('does not run early terminating actions twice, or cancel them', function (done){ + var mock = Object.create(Future.prototype); + mock._interpret = function (_, l, r){ return r(done()) || (function (){ return done(error) }) }; + var m = through('a', [ + after(30), + map(function (x){ return (x + 'b') }), + race(mock), + ]); + m._interpret(done, noop, noop); + }); + + it('does not run concurrent computations twice', function (done){ + var ran = false; + var m = through(resolvedSlow, [ + chain(function (){ return resolvedSlow }), + race(Future(function (){ ran ? done(error) : (ran = true) })), + ]); + m._interpret(done, done, function (){ return done() }); + }); + + it('returns a cancel function which cancels all running actions', function (done){ + var i = 0; + var started = function (){ return void i++ }; + var cancelled = function (){ return --i < 1 && done() }; + var slow = Future(function (){ return started() || (function (){ return cancelled() }) }); + var m = through(slow, [race(slow), race(slow), race(slow), race(slow)]); + var cancel = m._interpret(done, noop, noop); + eq(i, 5); + cancel(); + }); + +}); diff --git a/test/prop/0.algebra.mjs b/test/prop/0.algebra.mjs index 3cd07c65..d6221136 100644 --- a/test/prop/0.algebra.mjs +++ b/test/prop/0.algebra.mjs @@ -1,5 +1,5 @@ import show from 'sanctuary-show'; -import {assertEqual as eq, I, B, T, K} from '../util/util'; +import {assertEqual, I, B, T, K} from '../util/util'; import {FutureArb, any as _x, anyFuture as _mx, f, g, property, _of, elements} from '../util/props'; import { alt, @@ -22,22 +22,28 @@ var _f = elements([f, g, I, resolve]); var _mf = _of(_f); var _fm = FutureArb(_f, _f).smap(function (m){ return function (x){ - return bimap(T(x), T(x), m); + return bimap(T(x))(T(x))(m); }; }, function (f){ - return bimap(K, K, f()); + return bimap(K)(K)(f()); }, show); +function eq (x){ + return function (y){ + return assertEqual(x, y); + }; +} + describe('Algebra', function (){ describe('alt', function (){ property('associativity', _mx, _mx, _mx, function (a, b, c){ - return eq(alt(a, alt(b, c)), alt(alt(a, b), c)); + return eq(alt(a)(alt(b)(c)))(alt(alt(a)(b))(c)); }); property('distributivity with map', _mx, _mx, function (a, b){ - return eq(map(f, alt(a, b)), alt(map(f, a), map(f, b))); + return eq(map(f)(alt(a)(b)))(alt(map(f)(a))(map(f)(b))); }); }); @@ -45,11 +51,11 @@ describe('Algebra', function (){ describe('and', function (){ property('associativity', _mx, _mx, _mx, function (a, b, c){ - return eq(and(a, and(b, c)), and(and(a, b), c)); + return eq(and(a)(and(b)(c)))(and(and(a)(b))(c)); }); property('distributivity with map', _mx, _mx, function (a, b){ - return eq(map(f, and(a, b)), and(map(f, a), map(f, b))); + return eq(map(f)(and(a)(b)))(and(map(f)(a))(map(f)(b))); }); }); @@ -57,7 +63,7 @@ describe('Algebra', function (){ describe('ap', function (){ property('composition using map', _mx, _mf, _mf, function (mx, mf, mg){ - return eq(ap(ap(map(B, mg), mf), mx), ap(mg, ap(mf, mx))); + return eq(ap(mx)(ap(mf)(map(B)(mg))))(ap(ap(mx)(mf))(mg)); }); }); @@ -65,11 +71,11 @@ describe('Algebra', function (){ describe('bimap', function (){ property('identity', _mx, function (mx){ - return eq(bimap(I, I, mx), mx); + return eq(bimap(I)(I)(mx))(mx); }); property('composition', _mx, _f, _f, _f, _f, function (mx, f, g, h, i){ - return eq(bimap(B(f)(g), B(h)(i), mx), bimap(f, h, bimap(g, i, mx))); + return eq(bimap(B(f)(g))(B(h)(i))(mx))(bimap(f)(h)(bimap(g)(i)(mx))); }); }); @@ -77,7 +83,7 @@ describe('Algebra', function (){ describe('cache', function (){ property('idempotence', _mx, function (m){ - return eq(cache(cache(m)), cache(m)); + return eq(cache(cache(m)))(cache(m)); }); }); @@ -85,15 +91,15 @@ describe('Algebra', function (){ describe('chain', function (){ property('associativity', _mx, _fm, _fm, function (m, f, g){ - return eq(chain(g, chain(f, m)), chain(B(chain(g))(f), m)); + return eq(chain(g)(chain(f)(m)))(chain(B(chain(g))(f))(m)); }); property('left identity', _x, _fm, function (x, f){ - return eq(chain(f, resolve(x)), f(x)); + return eq(chain(f)(resolve(x)))(f(x)); }); property('right identity', _mx, function (m){ - return eq(chain(resolve, m), m); + return eq(chain(resolve)(m))(m); }); }); @@ -101,15 +107,15 @@ describe('Algebra', function (){ describe('chainRej', function (){ property('associativity', _mx, _fm, _fm, function (m, f, g){ - return eq(chainRej(g, chainRej(f, m)), chainRej(B(chainRej(g))(f), m)); + return eq(chainRej(g)(chainRej(f)(m)))(chainRej(B(chainRej(g))(f))(m)); }); property('left identity', _x, _fm, function (x, f){ - return eq(chainRej(f, reject(x)), f(x)); + return eq(chainRej(f)(reject(x)))(f(x)); }); property('right identity', _mx, function (m){ - return eq(chainRej(reject, m), m); + return eq(chainRej(reject)(m))(m); }); }); @@ -117,7 +123,7 @@ describe('Algebra', function (){ describe('hook', function (){ property('identity', _mx, function (m){ - return eq(hook(m, resolve, resolve), m); + return eq(hook(m)(resolve)(resolve))(m); }); }); @@ -125,11 +131,11 @@ describe('Algebra', function (){ describe('lastly', function (){ property('associativity', _mx, _mx, _mx, function (a, b, c){ - return eq(lastly(a, lastly(b, c)), lastly(lastly(a, b), c)); + return eq(lastly(a)(lastly(b)(c)))(lastly(lastly(a)(b))(c)); }); property('distributivity with map', _mx, _mx, function (a, b){ - return eq(map(f, lastly(a, b)), lastly(map(f, a), map(f, b))); + return eq(map(f)(lastly(a)(b)))(lastly(map(f)(a))(map(f)(b))); }); }); @@ -137,11 +143,11 @@ describe('Algebra', function (){ describe('map', function (){ property('identity', _mx, function (m){ - return eq(map(I, m), m); + return eq(map(I)(m))(m); }); property('composition', _mx, _f, _f, function (m, f, g){ - return eq(map(B(f)(g), m), map(f, map(g, m))); + return eq(map(B(f)(g))(m))(map(f)(map(g)(m))); }); }); @@ -149,11 +155,11 @@ describe('Algebra', function (){ describe('mapRej', function (){ property('identity', _mx, function (m){ - return eq(mapRej(I, m), m); + return eq(mapRej(I)(m))(m); }); property('composition', _mx, _f, _f, function (m, f, g){ - return eq(mapRej(B(f)(g), m), mapRej(f, mapRej(g, m))); + return eq(mapRej(B(f)(g))(m))(mapRej(f)(mapRej(g)(m))); }); }); @@ -161,15 +167,15 @@ describe('Algebra', function (){ describe('resolve', function (){ property('identity for ap', _mx, function (mx){ - return eq(ap(resolve(I), mx), mx); + return eq(ap(mx)(resolve(I)))(mx); }); property('homomorphism with ap', _x, function (x){ - return eq(ap(resolve(f), resolve(x)), resolve(f(x))); + return eq(ap(resolve(x))(resolve(f)))(resolve(f(x))); }); property('interchange with ap', _x, _mf, function (x, mf){ - return eq(ap(mf, resolve(x)), ap(resolve(T(x)), mf)); + return eq(ap(resolve(x))(mf))(ap(mf)(resolve(T(x)))); }); }); @@ -177,7 +183,7 @@ describe('Algebra', function (){ describe('swap', function (){ property('self inverse', _mx, function (m){ - return eq(swap(swap(m)), m); + return eq(swap(swap(m)))(m); }); }); diff --git a/test/prop/0.fantasy-land.mjs b/test/prop/0.fantasy-land.mjs index 53922f0c..f8726206 100644 --- a/test/prop/0.fantasy-land.mjs +++ b/test/prop/0.fantasy-land.mjs @@ -25,10 +25,10 @@ var _f = elements([f, g, I, of]); var _mf = _of(_f); var _fm = FutureArb(_f, _f).smap(function (m){ return function (x){ - return bimap(T(x), T(x), m); + return bimap(T(x))(T(x))(m); }; }, function (f){ - return bimap(K, K, f()); + return bimap(K)(K)(f()); }, show); function test (laws, name){ diff --git a/test/prop/1.fantasy-libs.mjs b/test/prop/1.fantasy-libs.mjs index 94c5b151..498f7864 100644 --- a/test/prop/1.fantasy-libs.mjs +++ b/test/prop/1.fantasy-libs.mjs @@ -25,42 +25,42 @@ var make = oneof(constant(resolve), constant(reject)); describe('Libs', function (){ - property('Z.of(Future, x) = Future.of(x)', any, function (x){ - return assertEqual(Z.of(Future, x), Future.of(x)); + property('Z.of(Future, x) = resolve(x)', any, function (x){ + return assertEqual(Z.of(Future, x), resolve(x)); }); - property('R.ap(mf, mx) = ap(mf, mx)', stringSquareFuture, stringNumberFuture, function (mf, mx){ - return assertEqual(R.ap(mf, mx), ap(mf, mx)); + property('R.ap(mf, mx) = ap(mx)(mf)', stringSquareFuture, stringNumberFuture, function (mf, mx){ + return assertEqual(R.ap(mf, mx), ap(mx)(mf)); }); - property('Z.ap(mf, mx) = ap(mf, mx)', stringSquareFuture, stringNumberFuture, function (mf, mx){ - return assertEqual(Z.ap(mf, mx), ap(mf, mx)); + property('Z.ap(mf, mx) = ap(mx)(mf)', stringSquareFuture, stringNumberFuture, function (mf, mx){ + return assertEqual(Z.ap(mf, mx), ap(mx)(mf)); }); - property('Z.alt(a, b) = alt(a, b)', anyFuture, anyFuture, function (a, b){ - return assertEqual(Z.alt(a, b), alt(a, b)); + property('Z.alt(a, b) = alt(b)(a)', anyFuture, anyFuture, function (a, b){ + return assertEqual(Z.alt(a, b), alt(b)(a)); }); - property('R.map(f, mx) = map(f, mx)', stringNumberFuture, function (mx){ - return assertEqual(R.map(square, mx), map(square, mx)); + property('R.map(f, mx) = map(f)(mx)', stringNumberFuture, function (mx){ + return assertEqual(R.map(square, mx), map(square)(mx)); }); property('Z.map(f, mx) = map(f, mx)', stringNumberFuture, function (mx){ - return assertEqual(Z.map(square, mx), map(square, mx)); + return assertEqual(Z.map(square, mx), map(square)(mx)); }); - property('Z.bimap(f, g, mx) = bimap(f, g, mx)', stringNumberFuture, function (mx){ - return assertEqual(Z.bimap(bang, square, mx), bimap(bang, square, mx)); + property('Z.bimap(f, g, mx) = bimap(f)(g)(mx)', stringNumberFuture, function (mx){ + return assertEqual(Z.bimap(bang, square, mx), bimap(bang)(square)(mx)); }); - property('R.chain(f, mx) = chain(f, mx)', make, stringNumberFuture, function (f, mx){ - var g = compose(f, square); - return assertEqual(R.chain(g, mx), chain(g, mx)); + property('R.chain(f, mx) = chain(f)(mx)', make, stringNumberFuture, function (g, mx){ + var f = compose(f, square); + return assertEqual(R.chain(f, mx), chain(f)(mx)); }); - property('Z.chain(f, mx) = chain(f, mx)', make, stringNumberFuture, function (f, mx){ - var g = compose(f, square); - return assertEqual(Z.chain(g, mx), chain(g, mx)); + property('Z.chain(f, mx) = chain(f)(mx)', make, stringNumberFuture, function (g, mx){ + var f = compose(f, square); + return assertEqual(Z.chain(f, mx), chain(f)(mx)); }); }); diff --git a/test/prop/1.fantasy-relations.mjs b/test/prop/1.fantasy-relations.mjs index 3477a299..46cc5ec4 100644 --- a/test/prop/1.fantasy-relations.mjs +++ b/test/prop/1.fantasy-relations.mjs @@ -1,4 +1,4 @@ -import {assertEqual as eq, I, B, K} from '../util/util'; +import {assertEqual, I, B, K} from '../util/util'; import { any, anyFuture, @@ -11,7 +11,6 @@ import { property, } from '../util/props'; import { - Future, after, and, bimap, @@ -29,66 +28,68 @@ import { var make = oneof(constant(resolve), constant(reject)); -describe('Prop', function (){ +function eq (a){ + return function (b){ + return assertEqual(a, b); + }; +} - property('Rejected m => swap(m) = chainRej(resolve, m)', anyRejectedFuture, function (m){ - return eq(swap(m), chainRej(resolve, m)); - }); +describe('Prop', function (){ - property('Resolved m => swap(m) = chain(reject, m)', anyResolvedFuture, function (m){ - return eq(swap(m), chain(reject, m)); + property('Rejected m => swap(m) = chainRej(resolve)(m)', anyRejectedFuture, function (m){ + return eq(swap(m))(chainRej(resolve)(m)); }); - property('reject(x) = swap(Future.of(x))', any, function (x){ - return eq(reject(x), swap(Future.of(x))); + property('Resolved m => swap(m) = chain(reject)(m)', anyResolvedFuture, function (m){ + return eq(swap(m))(chain(reject)(m)); }); - property('swap(reject(x)) = Future.of(x)', any, function (x){ - return eq(swap(reject(x)), Future.of(x)); + property('reject(x) = swap(resolve(x))', any, function (x){ + return eq(reject(x))(swap(resolve(x))); }); - property('Resolved m => chainRej(B(mk)(f), m) = m', make, anyResolvedFuture, function (mk, m){ - return eq(chainRej(B(mk)(f), m), m); + property('swap(reject(x)) = resolve(x)', any, function (x){ + return eq(swap(reject(x)))(resolve(x)); }); - property('Rejected m => chainRej(B(mk)(f), m) = swap(chain(B(mk)(f), swap(m)))', make, anyRejectedFuture, function (mk, m){ - return eq(chainRej(B(mk)(f), m), chain(B(mk)(f), swap(m))); + property('Resolved m => chainRej(B(mk)(f))(m) = m', make, anyResolvedFuture, function (mk, m){ + return eq(chainRej(B(mk)(f))(m))(m); }); - property('after(1, x) = Future.of(x)', any, function (n, x){ - return eq(after(1, x), Future.of(x)); + property('Rejected m => chainRej(B(mk)(f))(m) = swap(chain(B(mk)(f))(swap(m)))', make, anyRejectedFuture, function (mk, m){ + return eq(chainRej(B(mk)(f))(m))(chain(B(mk)(f))(swap(m))); }); - property('and(a, b) = chain(K(b), a)', anyFuture, anyFuture, function (a, b){ - return eq(and(a, b), chain(K(b), a)); + property('after(1)(x) = resolve(x)', any, function (n, x){ + return eq(after(1)(x))(resolve(x)); }); - property('Rejected m => fold(f, g, m) = chainRej(B(Future.of)(f), m)', anyRejectedFuture, function (m){ - return eq(fold(f, g, m), chainRej(B(Future.of)(f), m)); + property('and(a)(b) = chain(K(a))(b)', anyFuture, anyFuture, function (a, b){ + return eq(and(a)(b))(chain(K(a))(b)); }); - property('Resolved m => fold(f, g, m) = map(g, m)', anyResolvedFuture, function (m){ - return eq(fold(f, g, m), map(g, m)); + property('Rejected m => fold(f)(g)(m) = chainRej(B(resolve)(f))(m)', anyRejectedFuture, function (m){ + return eq(fold(f)(g)(m))(chainRej(B(resolve)(f))(m)); }); - property('go(function*(){ return f(yield m) }) = map(f, m)', anyFuture, function (m){ - return eq(go(function*(){ return f(yield m) }), map(f, m)); + property('Resolved m => fold(f)(g)(m) = map(g)(m)', anyResolvedFuture, function (m){ + return eq(fold(f)(g)(m))(map(g)(m)); }); - property('mapRej(f, m) = chainRej(B(reject)(f), m)', anyFuture, function (m){ - return eq(mapRej(f, m), chainRej(B(reject)(f), m)); + property('go(function*(){ return f(yield m) }) = map(f)(m)', anyFuture, function (m){ + return eq(go(function*(){ return f(yield m) }))(map(f)(m)); }); - property('mapRej(f, m) = bimap(f, I, m)', anyFuture, function (m){ - return eq(mapRej(f, m), bimap(f, I, m)); + property('mapRej(f)(m) = chainRej(B(reject)(f))(m)', anyFuture, function (m){ + return eq(mapRej(f)(m))(chainRej(B(reject)(f))(m)); }); - property('resolve(x) = Future.of(x)', any, function (x){ - return eq(resolve(x), Future.of(x)); + property('mapRej(f)(m) = bimap(f)(I)(m)', anyFuture, function (m){ + return eq(mapRej(f)(m))(bimap(f)(I)(m)); }); - property('rejectAfter(1, x) = reject(x)', any, function (n, x){ - return eq(rejectAfter(1, x), reject(x)); + property('rejectAfter(1)(x) = reject(x)', any, function (n, x){ + return eq(rejectAfter(1)(x))(reject(x)); }); }); diff --git a/test/unit.js b/test/unit.js index 04adb049..dfea8816 100644 --- a/test/unit.js +++ b/test/unit.js @@ -4,53 +4,46 @@ import './unit/0.iteration.mjs'; import './unit/0.list.mjs'; import './unit/0.predicates.mjs'; import './unit/0.utils.mjs'; -import './unit/1.done.mjs'; -import './unit/1.extract-left.mjs'; -import './unit/1.extract-right.mjs'; -import './unit/1.fork-catch.mjs'; -import './unit/1.fork.mjs'; +import './unit/1.future.mjs'; +import './unit/1.is-future.mjs'; import './unit/1.is-never.mjs'; import './unit/1.never.mjs'; -import './unit/1.promise.mjs'; -import './unit/1.value.mjs'; -import './unit/2.crashed.mjs'; -import './unit/2.future.mjs'; -import './unit/2.methods.mjs'; -import './unit/2.transformation.mjs'; +import './unit/1.pipe.mjs'; +import './unit/2.done.mjs'; +import './unit/2.extract-left.mjs'; +import './unit/2.extract-right.mjs'; +import './unit/2.fork-catch.mjs'; +import './unit/2.fork.mjs'; +import './unit/2.promise.mjs'; +import './unit/2.value.mjs'; import './unit/3.after.mjs'; -import './unit/3.of.mjs'; +import './unit/3.attempt-p.mjs'; +import './unit/3.attempt.mjs'; +import './unit/3.cache.mjs'; +import './unit/3.crash.mjs'; +import './unit/3.encase-p.mjs'; +import './unit/3.encase.mjs'; +import './unit/3.go.mjs'; +import './unit/3.hook.mjs'; +import './unit/3.node.mjs'; +import './unit/3.parallel.mjs'; import './unit/3.reject-after.mjs'; import './unit/3.reject.mjs'; -import './unit/4.chain-rec.mjs'; -import './unit/5.alt.mjs'; -import './unit/5.and.mjs'; -import './unit/5.ap.mjs'; -import './unit/5.attempt.mjs'; -import './unit/5.bimap.mjs'; -import './unit/5.both.mjs'; -import './unit/5.cache.mjs'; -import './unit/5.chain-rej.mjs'; -import './unit/5.chain.mjs'; -import './unit/5.encase-n.mjs'; -import './unit/5.encase-n2.mjs'; -import './unit/5.encase-n3.mjs'; -import './unit/5.encase-p.mjs'; -import './unit/5.encase-p2.mjs'; -import './unit/5.encase-p3.mjs'; -import './unit/5.encase.mjs'; -import './unit/5.encase2.mjs'; -import './unit/5.encase3.mjs'; -import './unit/5.fold.mjs'; -import './unit/5.hook.mjs'; -import './unit/5.lastly.mjs'; -import './unit/5.map-rej.mjs'; -import './unit/5.map.mjs'; -import './unit/5.node.mjs'; -import './unit/5.parallel-ap.mjs'; -import './unit/5.parallel.mjs'; -import './unit/5.race.mjs'; -import './unit/5.swap.mjs'; -import './unit/5.try-p.mjs'; -import './unit/6.go.mjs'; -import './unit/6.par.mjs'; -import './unit/6.seq.mjs'; +import './unit/3.resolve.mjs'; +import './unit/4.alt.mjs'; +import './unit/4.and.mjs'; +import './unit/4.ap.mjs'; +import './unit/4.bimap.mjs'; +import './unit/4.both.mjs'; +import './unit/4.chain-rej.mjs'; +import './unit/4.chain.mjs'; +import './unit/4.fold.mjs'; +import './unit/4.lastly.mjs'; +import './unit/4.map-rej.mjs'; +import './unit/4.map.mjs'; +import './unit/4.parallel-ap.mjs'; +import './unit/4.race.mjs'; +import './unit/4.swap.mjs'; +import './unit/5.chain-rec.mjs'; +import './unit/5.par.mjs'; +import './unit/5.seq.mjs'; diff --git a/test/unit/0.error.mjs b/test/unit/0.error.mjs index 61e7ec99..a7013026 100644 --- a/test/unit/0.error.mjs +++ b/test/unit/0.error.mjs @@ -7,7 +7,7 @@ import { typeError, invalidArgument, invalidContext, - invalidFuture, + invalidFutureArgument, makeError, contextToStackTrace } from '../../src/internal/error'; @@ -51,7 +51,7 @@ describe('error', function (){ }); - describe('invalidFuture', function (){ + describe('invalidFutureArgument', function (){ var mockType = function (identifier){ return {'constructor': {'@@type': identifier}, '@@show': function (){ @@ -59,40 +59,29 @@ describe('error', function (){ }}; }; - it('creates a TypeError with a computed message', function (){ - var actual = invalidFuture( - 'DeepThought', 'the answer to be 42', 43, - '\n See: https://en.wikipedia.org/wiki/Off-by-one_error' - ); - eq(actual, new TypeError( - 'DeepThought() expects the answer to be 42.\n Actual: 43 :: Number\n' + - ' See: https://en.wikipedia.org/wiki/Off-by-one_error' - )); - }); - it('warns us when nothing seems wrong', function (){ - var actual = invalidFuture('Foo', 0, mockType(namespace + '/' + name + '@' + version)); + var actual = invalidFutureArgument('Foo', 0, mockType(namespace + '/' + name + '@' + version)); eq(actual, new TypeError( 'Foo() expects its first argument to be a valid Future.\n' + 'Nothing seems wrong. Contact the Fluture maintainers.\n' + - ' Actual: mockType("fluture/Future@4") :: Future' + ' Actual: mockType("fluture/Future@5") :: Future' )); }); it('Warns us about Futures from other sources', function (){ - var actual = invalidFuture('Foo', 0, mockType('bobs-tinkershop/' + name + '@' + version)); + var actual = invalidFutureArgument('Foo', 0, mockType('bobs-tinkershop/' + name + '@' + version)); eq(actual, new TypeError( 'Foo() expects its first argument to be a valid Future.\n' + 'The Future was not created by fluture. ' + 'Make sure you transform other Futures to fluture Futures. ' + 'Got a Future from bobs-tinkershop.\n' + ' See: https://github.com/fluture-js/Fluture#casting-futures\n' + - ' Actual: mockType("bobs-tinkershop/Future@4") :: Future' + ' Actual: mockType("bobs-tinkershop/Future@5") :: Future' )); }); it('Warns us about Futures from unnamed sources', function (){ - var actual = invalidFuture('Foo', 0, mockType(name)); + var actual = invalidFutureArgument('Foo', 0, mockType(name)); eq(actual, new TypeError( 'Foo() expects its first argument to be a valid Future.\n' + 'The Future was not created by fluture. ' + @@ -104,26 +93,26 @@ describe('error', function (){ }); it('Warns about older versions', function (){ - var actual = invalidFuture('Foo', 0, mockType(namespace + '/' + name + '@' + (version - 1))); + var actual = invalidFutureArgument('Foo', 0, mockType(namespace + '/' + name + '@' + (version - 1))); eq(actual, new TypeError( 'Foo() expects its first argument to be a valid Future.\n' + 'The Future was created by an older version of fluture. ' + 'This means that one of the sources which creates Futures is outdated. ' + 'Update this source, or transform its created Futures to be compatible.\n' + ' See: https://github.com/fluture-js/Fluture#casting-futures\n' + - ' Actual: mockType("fluture/Future@3") :: Future' + ' Actual: mockType("fluture/Future@4") :: Future' )); }); it('Warns about newer versions', function (){ - var actual = invalidFuture('Foo', 0, mockType(namespace + '/' + name + '@' + (version + 1))); + var actual = invalidFutureArgument('Foo', 0, mockType(namespace + '/' + name + '@' + (version + 1))); eq(actual, new TypeError( 'Foo() expects its first argument to be a valid Future.\n' + 'The Future was created by a newer version of fluture. ' + 'This means that one of the sources which creates Futures is outdated. ' + 'Update this source, or transform its created Futures to be compatible.\n' + ' See: https://github.com/fluture-js/Fluture#casting-futures\n' + - ' Actual: mockType("fluture/Future@5") :: Future' + ' Actual: mockType("fluture/Future@6") :: Future' )); }); diff --git a/test/unit/0.predicates.mjs b/test/unit/0.predicates.mjs index c2f880c7..e856cbbd 100644 --- a/test/unit/0.predicates.mjs +++ b/test/unit/0.predicates.mjs @@ -5,7 +5,7 @@ import * as util from '../../src/internal/predicates'; var expect = chai.expect; -describe('is', function (){ +describe('predicates', function (){ describe('.isThenable()', function (){ @@ -32,11 +32,11 @@ describe('is', function (){ describe('.isFunction()', function (){ - var fs = [function (){}, function (){}, Future]; var xs = [NaN, 1, true, undefined, null, [], {}]; it('returns true when given a Function', function (){ - fs.forEach(function (f){ return expect(util.isFunction(f)).to.equal(true) }); + U.eq(util.isFunction(function (){}), true); + U.eq(util.isFunction(Future), true); }); it('returns false when not given a Function', function (){ diff --git a/test/unit/0.utils.mjs b/test/unit/0.utils.mjs index 881a684d..ca0d20d6 100644 --- a/test/unit/0.utils.mjs +++ b/test/unit/0.utils.mjs @@ -5,71 +5,6 @@ var expect = chai.expect; describe('fn', function (){ - describe('.padf()', function (){ - - it('left-pads string representations of functions', function (){ - var f = function (){ - return 42; - }; - - var input = f.toString(); - var inputLines = input.split('\n'); - var actualLines = fn.padf('--', input).split('\n'); - expect(actualLines[0]).to.equal(inputLines[0]); - expect(actualLines[1]).to.equal('--' + inputLines[1]); - expect(actualLines[2]).to.equal('--' + inputLines[2]); - }); - - }); - - describe('.partial1()', function (){ - - it('can partially apply binary functions', function (){ - function binary (a, b){ return a + b } - - expect(fn.partial1(binary, 1)(1)).to.equal(2); - }); - - it('can partially apply ternary functions', function (){ - function ternary (a, b, c){ return a + b + c } - - expect(fn.partial1(ternary, 1)(1, 1)).to.equal(3); - }); - - it('can partially apply quaternary functions', function (){ - function quaternary (a, b, c, d){ return a + b + c + d } - - expect(fn.partial1(quaternary, 1)(1, 1, 1)).to.equal(4); - }); - - }); - - describe('.partial2()', function (){ - - it('can partially apply ternary functions', function (){ - function ternary (a, b, c){ return a + b + c } - - expect(fn.partial2(ternary, 1, 1)(1)).to.equal(3); - }); - - it('can partially apply quaternary functions', function (){ - function quaternary (a, b, c, d){ return a + b + c + d } - - expect(fn.partial2(quaternary, 1, 1)(1, 1)).to.equal(4); - }); - - }); - - describe('.partial3()', function (){ - - it('can partially apply quaternary functions', function (){ - function quaternary (a, b, c, d){ return a + b + c + d } - - expect(fn.partial3(quaternary, 1, 1, 1)(1)).to.equal(4); - }); - - }); - describe('.setImmediateFallback()', function (){ it('calls the function with a value in under 25ms', function (done){ diff --git a/test/unit/1.done.mjs b/test/unit/1.done.mjs deleted file mode 100644 index bc5e9a6a..00000000 --- a/test/unit/1.done.mjs +++ /dev/null @@ -1,20 +0,0 @@ -import {done} from '../../index.mjs'; -import {testFunction, functionArg, futureArg} from '../util/props'; -import {eq, isFunction} from '../util/util'; -import {mock} from '../util/futures'; - -describe('done()', function (){ - testFunction('done', done, [functionArg, futureArg], isFunction); - - it('dispatches to #done()', function (fin){ - var a = function (){}; - var m = Object.create(mock); - - m.done = function (x){ - eq(x, a); - fin(); - }; - - done(a, m); - }); -}); diff --git a/test/unit/1.fork-catch.mjs b/test/unit/1.fork-catch.mjs deleted file mode 100644 index 80c6377c..00000000 --- a/test/unit/1.fork-catch.mjs +++ /dev/null @@ -1,25 +0,0 @@ -import {forkCatch} from '../../index.mjs'; -import {testFunction, functionArg, futureArg} from '../util/props'; -import {eq, isFunction} from '../util/util'; -import {mock} from '../util/futures'; - -describe('forkCatch()', function (){ - testFunction('forkCatch', forkCatch, [functionArg, functionArg, functionArg, futureArg], isFunction); - - it('dispatches to #_interpret()', function (){ - var a = function (){}; - var b = function (){}; - var c = function (){}; - var d = function (){}; - var m = Object.create(mock); - - m._interpret = function (rec, rej, res){ - eq(rec, a); - eq(rej, b); - eq(res, c); - return d; - }; - - eq(forkCatch(a, b, c, m), d); - }); -}); diff --git a/test/unit/1.fork.mjs b/test/unit/1.fork.mjs deleted file mode 100644 index 590a2151..00000000 --- a/test/unit/1.fork.mjs +++ /dev/null @@ -1,24 +0,0 @@ -import {fork} from '../../index.mjs'; -import {testFunction, functionArg, futureArg} from '../util/props'; -import {eq, isFunction} from '../util/util'; -import {mock} from '../util/futures'; - -describe('fork()', function (){ - testFunction('fork', fork, [functionArg, functionArg, futureArg], isFunction); - - it('dispatches to #_interpret()', function (){ - var a = function (){}; - var b = function (){}; - var c = function (){}; - var m = Object.create(mock); - - m._interpret = function (rec, rej, res){ - eq(typeof rec, 'function'); - eq(rej, a); - eq(res, b); - return c; - }; - - eq(fork(a, b, m), c); - }); -}); diff --git a/test/unit/2.future.mjs b/test/unit/1.future.mjs similarity index 97% rename from test/unit/2.future.mjs rename to test/unit/1.future.mjs index 027215fc..c607db28 100644 --- a/test/unit/2.future.mjs +++ b/test/unit/1.future.mjs @@ -73,7 +73,7 @@ describe('Future()', function (){ describe('#toString()', function (){ it('returns a customized representation', function (){ var m = Future(function (rej, res){ res() }); - eq(m.toString(), 'Future(function (rej, res){ res() })'); + eq(m.toString(), 'Future (function (rej, res){ res() })'); }); }); diff --git a/test/unit/1.pipe.mjs b/test/unit/1.pipe.mjs new file mode 100644 index 00000000..f2b67998 --- /dev/null +++ b/test/unit/1.pipe.mjs @@ -0,0 +1,19 @@ +import {mock} from '../util/futures'; +import {eq, throws} from '../util/util'; + +describe('Future#pipe()', function (){ + + it('throws when not given a function', function (){ + throws(function (){ + mock.pipe(42); + }, new TypeError( + 'Future#pipe() expects its first argument to be a Function.\n' + + ' Actual: 42 :: Number' + )); + }); + + it('transforms the Future using the given function', function (){ + eq(mock.pipe(String), '(util.mock)'); + }); + +}); diff --git a/test/unit/1.promise.mjs b/test/unit/1.promise.mjs deleted file mode 100644 index 2d29a6ba..00000000 --- a/test/unit/1.promise.mjs +++ /dev/null @@ -1,23 +0,0 @@ -import {promise} from '../../index.mjs'; -import {testFunction, futureArg} from '../util/props'; -import {noop, isThenable} from '../util/util'; -import {mock} from '../util/futures'; - -describe('promise()', function (){ - - before('setup global promise handler', function (){ - process.addListener('unhandledRejection', noop); - }); - - after('remove global promise handler', function (){ - process.removeListener('unhandledRejection', noop); - }); - - testFunction('promise', promise, [futureArg], isThenable); - - it('dispatches to #promise', function (done){ - var m = Object.create(mock); - m.promise = done; - promise(m); - }); -}); diff --git a/test/unit/1.value.mjs b/test/unit/1.value.mjs deleted file mode 100644 index 03b4a0b9..00000000 --- a/test/unit/1.value.mjs +++ /dev/null @@ -1,20 +0,0 @@ -import {value} from '../../index.mjs'; -import {testFunction, functionArg, resolvedFutureArg} from '../util/props'; -import {eq, isFunction} from '../util/util'; -import {mock} from '../util/futures'; - -describe('value()', function (){ - testFunction('value', value, [functionArg, resolvedFutureArg], isFunction); - - it('dispatches to #value()', function (done){ - var a = function (){}; - var m = Object.create(mock); - - m.value = function (x){ - eq(x, a); - done(); - }; - - value(a, m); - }); -}); diff --git a/test/unit/2.crashed.mjs b/test/unit/2.crashed.mjs deleted file mode 100644 index 9b7d2b70..00000000 --- a/test/unit/2.crashed.mjs +++ /dev/null @@ -1,12 +0,0 @@ -import show from 'sanctuary-show'; -import {Crashed} from '../../src/future'; -import {eq, assertValidFuture} from '../util/util'; - -describe('Crashed', function (){ - - it('behaves', function (){ - assertValidFuture(new Crashed(42)); - eq(show(new Crashed(42)), 'Future(function crash(){ throw 42 })'); - }); - -}); diff --git a/test/unit/2.done.mjs b/test/unit/2.done.mjs new file mode 100644 index 00000000..65e3e56b --- /dev/null +++ b/test/unit/2.done.mjs @@ -0,0 +1,24 @@ +import {done} from '../../index.mjs'; +import {testFunction, functionArg, resolvedFutureArg} from '../util/props'; +import {eq, isFunction} from '../util/util'; +import {rejected, resolved} from '../util/futures'; + +describe('done()', function (){ + testFunction('done', done, [functionArg, resolvedFutureArg], isFunction); + + it('passes the rejection value as first parameter', function (testDone){ + done(function (x, y){ + eq(x, 'rejected'); + eq(y, undefined); + testDone(); + })(rejected); + }); + + it('passes the resolution value as second parameter', function (testDone){ + done(function (x, y){ + eq(x, null); + eq(y, 'resolved'); + testDone(); + })(resolved); + }); +}); diff --git a/test/unit/1.extract-left.mjs b/test/unit/2.extract-left.mjs similarity index 100% rename from test/unit/1.extract-left.mjs rename to test/unit/2.extract-left.mjs diff --git a/test/unit/1.extract-right.mjs b/test/unit/2.extract-right.mjs similarity index 100% rename from test/unit/1.extract-right.mjs rename to test/unit/2.extract-right.mjs diff --git a/test/unit/2.fork-catch.mjs b/test/unit/2.fork-catch.mjs new file mode 100644 index 00000000..c09fd71e --- /dev/null +++ b/test/unit/2.fork-catch.mjs @@ -0,0 +1,32 @@ +import {forkCatch} from '../../index.mjs'; +import {testFunction, functionArg, futureArg} from '../util/props'; +import {eq, isFunction, error, noop} from '../util/util'; +import {crashed, rejected, resolved} from '../util/futures'; + +describe('forkCatch()', function (){ + testFunction('forkCatch', forkCatch, [functionArg, functionArg, functionArg, futureArg], isFunction); + + it('calls the first continuation with the crash exception', function (done){ + function assertCrash (x){ + eq(x, error); + done(); + } + forkCatch(assertCrash)(noop)(noop)(crashed); + }); + + it('calls the second continuation with the rejection reason', function (done){ + function assertRejection (x){ + eq(x, 'rejected'); + done(); + } + forkCatch(noop)(assertRejection)(noop)(rejected); + }); + + it('calls the third continuation with the resolution value', function (done){ + function assertResolution (x){ + eq(x, 'resolved'); + done(); + } + forkCatch(noop)(noop)(assertResolution)(resolved); + }); +}); diff --git a/test/unit/2.fork.mjs b/test/unit/2.fork.mjs new file mode 100644 index 00000000..3b3b0c01 --- /dev/null +++ b/test/unit/2.fork.mjs @@ -0,0 +1,28 @@ +import {fork} from '../../index.mjs'; +import {testFunction, functionArg, futureArg} from '../util/props'; +import {eq, isFunction, error, noop, itRaises} from '../util/util'; +import {crashed, rejected, resolved} from '../util/futures'; + +describe('fork()', function (){ + testFunction('fork', fork, [functionArg, functionArg, futureArg], isFunction); + + itRaises('the crash exception', function (){ + fork(noop)(noop)(crashed); + }, error); + + it('calls the first continuation with the rejection reason', function (done){ + function assertRejection (x){ + eq(x, 'rejected'); + done(); + } + fork(assertRejection)(noop)(rejected); + }); + + it('calls the second continuation with the resolution value', function (done){ + function assertResolution (x){ + eq(x, 'resolved'); + done(); + } + fork(noop)(assertResolution)(resolved); + }); +}); diff --git a/test/unit/2.methods.mjs b/test/unit/2.methods.mjs deleted file mode 100644 index 072beb18..00000000 --- a/test/unit/2.methods.mjs +++ /dev/null @@ -1,209 +0,0 @@ -import show from 'sanctuary-show'; -import {testMethod, futureArg, functionArg} from '../util/props'; -import {eq, noop, error, raises} from '../util/util'; -import {mock as instance} from '../util/futures'; - -describe('Future prototype', function (){ - - describe('and()', function (){ - testMethod(instance, 'and', [futureArg]); - }); - - describe('ap()', function (){ - testMethod(instance, 'ap', [futureArg]); - }); - - describe('bimap()', function (){ - testMethod(instance, 'bimap', [functionArg, functionArg]); - }); - - describe('both()', function (){ - testMethod(instance, 'both', [futureArg]); - }); - - describe('chain()', function (){ - testMethod(instance, 'chain', [functionArg]); - }); - - describe('chainRej()', function (){ - testMethod(instance, 'chainRej', [functionArg]); - }); - - describe('done()', function (){ - testMethod(instance, 'done', [functionArg]); - - it('passes the rejection value as first parameter', function (done){ - var mock = Object.create(instance); - mock._interpret = function (_, l){l(1)}; - mock.done(function (x, y){ - eq(x, 1); - eq(y, undefined); - done(); - }); - }); - - it('passes the resolution value as second parameter', function (done){ - var mock = Object.create(instance); - mock._interpret = function (_, l, r){r(1)}; - mock.done(function (x, y){ - eq(x, null); - eq(y, 1); - done(); - }); - }); - }); - - describe('finally()', function (){ - testMethod(instance, 'finally', [futureArg]); - }); - - describe('fold()', function (){ - testMethod(instance, 'fold', [functionArg, functionArg]); - }); - - describe('fork()', function (){ - testMethod(instance, 'fork', [functionArg, functionArg]); - - it('dispatches to #_interpret()', function (done){ - var a = function (){}; - var b = function (){}; - var mock = Object.create(instance); - - mock._interpret = function (rec, rej, res){ - eq(typeof rec, 'function'); - eq(rej, a); - eq(res, b); - done(); - }; - - mock.fork(a, b); - }); - - it('throws the interpretation crash value', function (done){ - var mock = Object.create(instance); - mock._interpret = function (rec){ rec(error) }; - raises(done, function (){ return mock.fork(noop, noop) }, error); - }); - }); - - describe('forkCatch()', function (){ - testMethod(instance, 'forkCatch', [functionArg, functionArg, functionArg]); - - it('dispatches to #_interpret()', function (done){ - var a = function (){}; - var b = function (){}; - var c = function (){}; - var mock = Object.create(instance); - - mock._interpret = function (rec, rej, res){ - eq(rec, a); - eq(rej, b); - eq(res, c); - done(); - }; - - mock.forkCatch(a, b, c); - }); - }); - - describe('lastly()', function (){ - testMethod(instance, 'lastly', [futureArg]); - }); - - describe('finally()', function (){ - testMethod(instance, 'finally', [futureArg]); - }); - - describe('map()', function (){ - testMethod(instance, 'map', [functionArg]); - }); - - describe('mapRej()', function (){ - testMethod(instance, 'mapRej', [functionArg]); - }); - - describe('alt()', function (){ - testMethod(instance, 'alt', [futureArg]); - }); - - describe('or()', function (){ - testMethod(instance, 'or', [futureArg]); - }); - - describe('promise()', function (){ - testMethod(instance, 'promise', []); - - it('returns a Promise', function (){ - var mock = Object.create(instance); - mock._interpret = noop; - var actual = mock.promise(); - eq(actual instanceof Promise, true); - }); - - it('resolves if the Future resolves', function (done){ - var mock = Object.create(instance); - mock._interpret = function (_, l, r){ return r(1) }; - mock.promise().then( - function (x){ return (eq(x, 1), done()) }, - done - ); - }); - - it('rejects if the Future rejects', function (done){ - var mock = Object.create(instance); - mock._interpret = function (_, l){ return l(1) }; - mock.promise().then( - function (){ return done(new Error('It resolved')) }, - function (x){ return (eq(x, 1), done()) } - ); - }); - }); - - describe('race()', function (){ - testMethod(instance, 'race', [futureArg]); - }); - - describe('swap()', function (){ - testMethod(instance, 'swap', []); - }); - - describe('value()', function (){ - testMethod(instance, 'value', [functionArg]); - - it('dispatches to #_interpret(), using the input as resolution callback', function (done){ - var res = function (){}; - var mock = Object.create(instance); - - mock._interpret = function (rec, l, r){ - eq(typeof rec, 'function'); - eq(typeof l, 'function'); - eq(r, res); - done(); - }; - - mock.value(res); - }); - - it('throws when _interpret calls the rejection callback', function (done){ - var mock = Object.create(instance); - mock._interpret = function (rec, rej){rej(1)}; - raises(done, mock.value.bind(mock, noop), new Error( - 'Future#value was called on a rejected Future\n' + - ' Rejection: 1\n' + - ' Future: ' + show(instance) - )); - }); - }); - - describe('pipe()', function (){ - testMethod(instance, 'pipe', [functionArg]); - - it('applies the given function to itself', function (done){ - instance.pipe(function (x){ - eq(x, instance); - done(); - }); - }); - }); - -}); diff --git a/test/unit/2.promise.mjs b/test/unit/2.promise.mjs new file mode 100644 index 00000000..9b010aa7 --- /dev/null +++ b/test/unit/2.promise.mjs @@ -0,0 +1,41 @@ +import {promise} from '../../index.mjs'; +import {testFunction, futureArg} from '../util/props'; +import {noop, isThenable, eq, itRaises, error} from '../util/util'; +import {crashed, rejected, resolved} from '../util/futures'; + +describe('promise()', function (){ + + before('setup global promise handler', function (){ + process.addListener('unhandledRejection', noop); + }); + + after('remove global promise handler', function (){ + process.removeListener('unhandledRejection', noop); + }); + + testFunction('promise', promise, [futureArg], isThenable); + + it('returns a Promise', function (){ + var actual = promise(resolved); + eq(actual instanceof Promise, true); + }); + + itRaises('if the Future crashes', function (){ + promise(crashed); + }, error); + + it('resolves if the Future resolves', function (done){ + promise(resolved).then( + function (x){ eq(x, 'resolved'); done() }, + done + ); + }); + + it('rejects if the Future rejects', function (done){ + promise(rejected).then( + function (){ return done(new Error('It resolved')) }, + function (x){ return (eq(x, 'rejected'), done()) } + ); + }); + +}); diff --git a/test/unit/2.transformation.mjs b/test/unit/2.transformation.mjs deleted file mode 100644 index 54607f8b..00000000 --- a/test/unit/2.transformation.mjs +++ /dev/null @@ -1,471 +0,0 @@ -import {Future, of, after} from '../../index.mjs'; -import chai from 'chai'; -import {add, bang, noop, error, assertResolved, assertRejected, assertCrashed, assertValidFuture} from '../util/util'; -import {resolved, rejected, resolvedSlow} from '../util/futures'; -import {Transformation} from '../../src/future'; -import {nil} from '../../src/internal/list'; -import State from 'fantasy-states'; - -var expect = chai.expect; -var StateT = State.StateT; - -describe('Transformation', function (){ - - var dummy = new Transformation(resolved, nil); - var rejectedDummy = new Transformation(rejected, nil); - var throwing = function (){ throw error }; - - it('behaves', function (){ - assertValidFuture(dummy); - assertValidFuture(rejectedDummy); - }); - - describe('ap', function (){ - - var seq = of(bang).ap(dummy); - - describe('#_interpret()', function (){ - - it('runs the action', function (){ - return assertResolved(seq, 'resolved!'); - }); - - }); - - describe('#toString()', function (){ - - it('returns code to create the same data-structure', function (){ - var expected = 'Future.of(' + bang.toString() + ').ap(Future.of("resolved"))'; - expect(seq.toString()).to.equal(expected); - }); - - }); - - }); - - describe('map', function (){ - - var seq = dummy.map(bang); - - describe('#_interpret()', function (){ - - it('crashes if the mapper throws', function (){ - return assertCrashed(dummy.map(throwing), error); - }); - - it('runs the action', function (){ - return assertResolved(seq, 'resolved!'); - }); - - }); - - describe('#toString()', function (){ - - it('returns code to create the same data-structure', function (){ - expect(seq.toString()).to.equal('Future.of("resolved").map(' + bang.toString() + ')'); - }); - - }); - - }); - - describe('bimap', function (){ - - var seq = dummy.bimap(add(1), bang); - - describe('#_interpret()', function (){ - - it('crashes if the left mapper throws', function (){ - return assertCrashed(rejectedDummy.bimap(throwing, noop), error); - }); - - it('crashes if the right mapper throws', function (){ - return assertCrashed(dummy.bimap(noop, throwing), error); - }); - - it('runs the action', function (){ - return assertResolved(seq, 'resolved!'); - }); - - }); - - describe('#toString()', function (){ - - it('returns code to create the same data-structure', function (){ - expect(seq.toString()).to.equal('Future.of("resolved").bimap(' + add(1).toString() + ', ' + bang.toString() + ')'); - }); - - }); - - }); - - describe('chain', function (){ - - var f = function (x){ return of(bang(x)) }; - var seq = dummy.chain(f); - - describe('#_interpret()', function (){ - - it('crashes if the mapper throws', function (){ - return assertCrashed(dummy.chain(throwing), error); - }); - - it('runs the action', function (){ - return assertResolved(seq, 'resolved!'); - }); - - }); - - describe('#toString()', function (){ - - it('returns code to create the same data-structure', function (){ - expect(seq.toString()).to.equal('Future.of("resolved").chain(' + f.toString() + ')'); - }); - - }); - - }); - - describe('mapRej', function (){ - - var seq = dummy.mapRej(add(1)); - - describe('#_interpret()', function (){ - - it('crashes if the mapper throws', function (){ - return assertCrashed(rejectedDummy.mapRej(throwing), error); - }); - - it('runs the action', function (){ - return assertResolved(seq, 'resolved'); - }); - - }); - - describe('#toString()', function (){ - - it('returns code to create the same data-structure', function (){ - expect(seq.toString()).to.equal('Future.of("resolved").mapRej(' + add(1).toString() + ')'); - }); - - }); - - }); - - describe('chainRej', function (){ - - var f = function (){ return of(1) }; - var seq = dummy.chainRej(f); - - describe('#_interpret()', function (){ - - it('crashes if the mapper throws', function (){ - return assertCrashed(rejectedDummy.chainRej(throwing), error); - }); - - it('runs the action', function (){ - return assertResolved(seq, 'resolved'); - }); - - }); - - describe('#toString()', function (){ - - it('returns code to create the same data-structure', function (){ - expect(seq.toString()).to.equal('Future.of("resolved").chainRej(' + f.toString() + ')'); - }); - - }); - - }); - - describe('race', function (){ - - var seq = dummy.race(dummy); - - describe('#_interpret()', function (){ - - it('runs the action', function (){ - return assertResolved(seq, 'resolved'); - }); - - }); - - describe('#toString()', function (){ - - it('returns code to create the same data-structure', function (){ - expect(seq.toString()).to.equal('Future.of("resolved").race(Future.of("resolved"))'); - }); - - }); - - }); - - describe('_parallelAp', function (){ - - var seq = of(bang)._parallelAp(dummy); - - describe('#_interpret()', function (){ - - it('runs the action', function (){ - return assertResolved(seq, 'resolved!'); - }); - - }); - - describe('#toString()', function (){ - - it('returns code to create the same data-structure', function (){ - var expected = 'Future.of(' + bang.toString() + ')._parallelAp(Future.of("resolved"))'; - expect(seq.toString()).to.equal(expected); - }); - - }); - - }); - - describe('both', function (){ - - var seq = dummy.both(dummy); - - describe('#_interpret()', function (){ - - it('runs the action', function (){ - return assertResolved(seq, ['resolved', 'resolved']); - }); - - }); - - describe('#toString()', function (){ - - it('returns code to create the same data-structure', function (){ - expect(seq.toString()).to.equal('Future.of("resolved").both(Future.of("resolved"))'); - }); - - }); - - }); - - describe('and', function (){ - - var seq = dummy.and(dummy); - - describe('#_interpret()', function (){ - - it('runs the action', function (){ - return assertResolved(seq, 'resolved'); - }); - - }); - - describe('#toString()', function (){ - - it('returns code to create the same data-structure', function (){ - expect(seq.toString()).to.equal('Future.of("resolved").and(Future.of("resolved"))'); - }); - - }); - - }); - - describe('or', function (){ - - var seq = dummy.or(dummy); - - describe('#_interpret()', function (){ - - it('runs the action', function (){ - return assertResolved(seq, 'resolved'); - }); - - }); - - describe('#toString()', function (){ - - it('returns code to create the same data-structure', function (){ - expect(seq.toString()).to.equal('Future.of("resolved").or(Future.of("resolved"))'); - }); - - }); - - }); - - describe('swap', function (){ - - var seq = dummy.swap(); - var nseq = new Transformation(rejected, nil).swap(); - - describe('#_interpret()', function (){ - - it('swaps from right to left', function (){ - return assertRejected(seq, 'resolved'); - }); - - it('swaps from left to right', function (){ - return assertResolved(nseq, 'rejected'); - }); - - }); - - describe('#toString()', function (){ - - it('returns code to create the same data-structure', function (){ - expect(seq.toString()).to.equal('Future.of("resolved").swap()'); - }); - - }); - - }); - - describe('fold', function (){ - - var seq = dummy.fold(function (){ return 0 }, function (){ return 1 }); - - describe('#_interpret()', function (){ - - it('crashes if the left mapper throws', function (){ - return assertCrashed(rejectedDummy.fold(throwing, noop), error); - }); - - it('crashes if the right mapper throws', function (){ - return assertCrashed(dummy.fold(noop, throwing), error); - }); - - it('runs the action', function (){ - return assertResolved(seq, 1); - }); - - }); - - describe('#toString()', function (){ - - it('returns code to create the same data-structure', function (){ - expect(seq.toString()).to.equal('Future.of("resolved").fold(function (){ return 0 }, function (){ return 1 })'); - }); - - }); - - }); - - describe('finally', function (){ - - var seq = dummy.finally(dummy); - - describe('#_interpret()', function (){ - - it('runs the action', function (){ - return assertResolved(seq, 'resolved'); - }); - - it('runs the other if the left rejects', function (done){ - var other = Future(function (){done()}); - var m = new Transformation(rejected, nil).finally(other); - m._interpret(done, noop, noop); - }); - - }); - - describe('#toString()', function (){ - - it('returns code to create the same data-structure', function (){ - expect(seq.toString()).to.equal('Future.of("resolved").finally(Future.of("resolved"))'); - }); - - }); - - }); - - describe('in general', function (){ - - describe('#_interpret()', function (){ - - it('is capable of joining', function (){ - var m = new Transformation(of('a'), nil) - //eslint-disable-next-line max-nested-callbacks - .chain(function (x){ return after(5, (x + 'b')).chain(function (x){ return after(5, (x + 'c')) }) }) - .chain(function (x){ return after(5, (x + 'd')) }) - .chain(function (x){ return of((x + 'e')) }) - .chain(function (x){ return after(5, (x + 'f')) }); - return assertResolved(m, 'abcdef'); - }); - - it('is capable of early termination', function (done){ - var slow = new Transformation(Future(function (){ - var id = setTimeout(done, 20, new Error('Not terminated')); - return function (){ return clearTimeout(id) }; - }), nil); - var m = slow.race(slow).race(slow).race(slow).race(resolved); - m._interpret(done, noop, noop); - setTimeout(done, 40, null); - }); - - it('cancels running actions when one early-terminates asynchronously', function (done){ - var slow = new Transformation(Future(function (){ - var id = setTimeout(done, 50, new Error('Not terminated')); - return function (){ return clearTimeout(id) }; - }), nil); - var m = slow.race(slow).race(slow).race(slow).race(resolvedSlow); - m._interpret(done, noop, noop); - setTimeout(done, 100, null); - }); - - it('does not run actions unnecessarily when one early-terminates synchronously', function (done){ - var broken = new Transformation(Future(function (){ done(error) }), nil); - var m = resolvedSlow.race(broken).race(broken).race(resolved); - m._interpret(done, noop, function (){ return done() }); - }); - - it('resolves the left-hand side first when running actions in parallel', function (){ - var m = new Transformation(of(1), nil).map(function (x){ return x }).chain(function (x){ return of(x) }); - return assertResolved(m.race(of(2)), 1); - }); - - it('does not forget about actions to run after early termination', function (){ - var m = new Transformation(after(30, 'a'), nil) - .race(new Transformation(after(20, 'b'), nil)) - .map(function (x){ return (x + 'c') }); - return assertResolved(m, 'bc'); - }); - - it('does not run early terminating actions twice, or cancel them', function (done){ - var mock = Object.create(Future.prototype); - mock._interpret = function (_, l, r){ return r(done()) || (function (){ return done(error) }) }; - var m = new Transformation(after(30, 'a'), nil).map(function (x){ return (x + 'b') }).race(mock); - m._interpret(done, noop, noop); - }); - - it('does not run concurrent computations twice', function (done){ - var ran = false; - var mock = Future(function (){ ran ? done(error) : (ran = true) }); - var m = new Transformation(resolvedSlow, nil).chain(function (){ return resolvedSlow }).race(mock); - m._interpret(done, done, function (){ return done() }); - }); - - it('returns a cancel function which cancels all running actions', function (done){ - var i = 0; - var started = function (){ return void i++ }; - var cancelled = function (){ return --i < 1 && done() }; - var slow = Future(function (){ return started() || (function (){ return cancelled() }) }); - var m = slow.race(slow).race(slow).race(slow).race(slow); - var cancel = m._interpret(done, noop, noop); - expect(i).to.equal(5); - cancel(); - }); - - }); - - }); - - describe('Bug 2017-06-02, reported by @d3vilroot', function (){ - - var Middleware = StateT(Future); - var slow = Middleware.lift(after(10, null)); - var program = slow.chain(function (){ return slow.chain(function (){ return slow }) }).evalState(null); - - it('does not occur', function (done){ - program._interpret(done, done, function (){ return done() }); - }); - - }); - -}); diff --git a/test/unit/2.value.mjs b/test/unit/2.value.mjs new file mode 100644 index 00000000..fe6ddbef --- /dev/null +++ b/test/unit/2.value.mjs @@ -0,0 +1,29 @@ +import show from 'sanctuary-show'; +import {value} from '../../index.mjs'; +import {testFunction, functionArg, resolvedFutureArg} from '../util/props'; +import {eq, isFunction, noop, itRaises, error} from '../util/util'; +import {crashed, rejected, resolved} from '../util/futures'; + +describe('value()', function (){ + testFunction('value', value, [functionArg, resolvedFutureArg], isFunction); + + itRaises('when the Future crashes', function (){ + value(noop)(rejected); + }, new Error( + 'Future#value was called on a rejected Future\n' + + ' Rejection: "rejected"\n' + + ' Future: ' + show(rejected) + )); + + itRaises('when the Future rejects', function (){ + value(noop)(crashed); + }, error); + + it('calls the continuation with the resolution value', function (done){ + value(function (x){ + eq(x, 'resolved'); + done(); + })(resolved); + }); + +}); diff --git a/test/unit/3.after.mjs b/test/unit/3.after.mjs index b169a920..66f12a64 100644 --- a/test/unit/3.after.mjs +++ b/test/unit/3.after.mjs @@ -1,45 +1,35 @@ -import chai from 'chai'; import {after, never} from '../../index.mjs'; -import * as U from '../util/util'; +import {eq, assertValidFuture, assertResolved, failRej, failRes} from '../util/util'; import {testFunction, positiveIntegerArg, anyArg} from '../util/props'; -var expect = chai.expect; - describe('after()', function (){ - testFunction('after', after, [positiveIntegerArg, anyArg], U.assertValidFuture); + testFunction('after', after, [positiveIntegerArg, anyArg], assertValidFuture); it('returns Never when given Infinity', function (){ - expect(after(Infinity, 1)).to.equal(never); + eq(after(Infinity)(1), never); }); describe('#_interpret()', function (){ - it('calls success callback with the value', function (){ - return U.assertResolved(after(20, 1), 1); + it('resolves with the given value', function (){ + return assertResolved(after(20)(1), 1); }); it('clears its internal timeout when cancelled', function (done){ - after(20, 1)._interpret(done, U.failRej, U.failRes)(); + after(20)(1)._interpret(done, failRej, failRes)(); setTimeout(done, 25); }); }); - describe('#swap()', function (){ - it('returns a rejected Future', function (){ - var m = after(10, 1); - return U.assertRejected(m.swap(), 1); - }); - }); - describe('#extractRight()', function (){ it('returns array with the value', function (){ - expect(after(20, 1).extractRight()).to.deep.equal([1]); + eq(after(20)(1).extractRight(), [1]); }); }); describe('#toString()', function (){ it('returns the code to create the After', function (){ - expect(after(20, 1).toString()).to.equal('after(20, 1)'); + eq(after(20)(1).toString(), 'after (20) (1)'); }); }); diff --git a/test/unit/5.try-p.mjs b/test/unit/3.attempt-p.mjs similarity index 60% rename from test/unit/5.try-p.mjs rename to test/unit/3.attempt-p.mjs index 2d82fad5..9626086a 100644 --- a/test/unit/5.try-p.mjs +++ b/test/unit/3.attempt-p.mjs @@ -1,61 +1,62 @@ /* eslint prefer-promise-reject-errors: 0 */ import chai from 'chai'; -import {tryP} from '../../index.mjs'; +import {attemptP, map, mapRej} from '../../index.mjs'; import * as U from '../util/util'; import {testFunction, functionArg} from '../util/props'; var expect = chai.expect; -describe('tryP()', function (){ +describe('attemptP()', function (){ - testFunction('tryP', tryP, [functionArg], U.assertValidFuture); + testFunction('encaseP', attemptP, [functionArg], U.assertValidFuture); describe('#_interpret()', function (){ it('crashes when the Promise generator throws', function (){ - var m = tryP(function (){ throw U.error }); + var m = attemptP(function (){ throw U.error }); return U.assertCrashed(m, U.error); }); it('crashes when the Promise generator does not return a Promise', function (){ - var m = tryP(U.noop); + var m = attemptP(U.noop); return U.assertCrashed(m, new TypeError( - 'tryP() expects the function it\'s given to return a Promise/Thenable\n' + + 'encaseP() expects the function it\'s given to return a Promise/Thenable\n' + ' Actual: undefined\n' + - ' From calling: function (){}' + ' From calling: function (){}\n' + + ' With: undefined' )); }); it('resolves with the resolution value of the returned Promise', function (){ - var actual = tryP(function (){ return Promise.resolve(1) }); + var actual = attemptP(function (){ return Promise.resolve(1) }); return U.assertResolved(actual, 1); }); it('rejects with rejection reason of the returned Promise', function (){ - var actual = tryP(function (){ return Promise.reject(U.error) }); + var actual = attemptP(function (){ return Promise.reject(U.error) }); return U.assertRejected(actual, U.error); }); it('ensures no resolution happens after cancel', function (done){ - var actual = tryP(function (){ return Promise.resolve(1) }); + var actual = attemptP(function (){ return Promise.resolve(1) }); actual._interpret(done, U.failRej, U.failRes)(); setTimeout(done, 20); }); it('ensures no rejection happens after cancel', function (done){ - var actual = tryP(function (){ return Promise.reject(1) }); + var actual = attemptP(function (){ return Promise.reject(1) }); actual._interpret(done, U.failRej, U.failRes)(); setTimeout(done, 20); }); it('crashes with errors that occur in rejection continuation', function (){ - var m = tryP(function (){ return Promise.resolve(1) }).map(function (){ throw U.error }); + var m = map(function (){ throw U.error })(attemptP(function (){ return Promise.resolve(1) })); return U.assertCrashed(m, U.error); }); it('crashes with errors that occur in resolution continuation', function (){ - var m = tryP(function (){ return Promise.reject(1) }).mapRej(function (){ throw U.error }); + var m = mapRej(function (){ throw U.error })(attemptP(function (){ return Promise.reject(1) })); return U.assertCrashed(m, U.error); }); @@ -65,8 +66,8 @@ describe('tryP()', function (){ it('returns the code to create the Future', function (){ var f = function (){ return Promise.resolve(42) }; - var m = tryP(f); - expect(m.toString()).to.equal('tryP(' + f.toString() + ')'); + var m = attemptP(f); + expect(m.toString()).to.equal('encaseP (' + f.toString() + ') (undefined)'); }); }); diff --git a/test/unit/5.attempt.mjs b/test/unit/3.attempt.mjs similarity index 76% rename from test/unit/5.attempt.mjs rename to test/unit/3.attempt.mjs index 319bcc17..87ee216d 100644 --- a/test/unit/5.attempt.mjs +++ b/test/unit/3.attempt.mjs @@ -1,5 +1,5 @@ import chai from 'chai'; -import {attempt} from '../../index.mjs'; +import {attempt, map} from '../../index.mjs'; import * as U from '../util/util'; import {testFunction, functionArg} from '../util/props'; @@ -7,7 +7,7 @@ var expect = chai.expect; describe('attempt()', function (){ - testFunction('attempt', attempt, [functionArg], U.assertValidFuture); + testFunction('encase', attempt, [functionArg], U.assertValidFuture); describe('#_interpret()', function (){ @@ -22,7 +22,7 @@ describe('attempt()', function (){ }); it('does not swallow errors from subsequent maps and such', function (){ - var m = attempt(function (x){ return x }).map(function (){ throw U.error }); + var m = map(function (){ throw U.error })(attempt(function (x){ return x })); return U.assertCrashed(m, U.error); }); @@ -33,7 +33,7 @@ describe('attempt()', function (){ it('returns the code to create the Future', function (){ var f = function (){}; var m = attempt(f); - expect(m.toString()).to.equal('attempt(' + f.toString() + ')'); + expect(m.toString()).to.equal('encase (' + f.toString() + ') (undefined)'); }); }); diff --git a/test/unit/5.cache.mjs b/test/unit/3.cache.mjs similarity index 85% rename from test/unit/5.cache.mjs rename to test/unit/3.cache.mjs index 1f39bcb6..dbcc3f27 100644 --- a/test/unit/5.cache.mjs +++ b/test/unit/3.cache.mjs @@ -1,6 +1,6 @@ import chai from 'chai'; -import {Future, cache, of, reject, after} from '../../index.mjs'; -import {Cached} from '../../src/cache'; +import {Future, cache, resolve, reject, after} from '../../index.mjs'; +import {Crashed, Rejected, Resolved} from '../../src/cache'; import * as U from '../util/util'; import * as F from '../util/futures'; import {testFunction, futureArg} from '../util/props'; @@ -17,11 +17,11 @@ describe('cache()', function (){ return U.assertCrashed(cache(F.crashed), U.error); }); - it('resolves with the resolution value of the given Future', function (){ - return U.assertResolved(cache(of(1)), 1); + it('resolves with the resolution value resolve the given Future', function (){ + return U.assertResolved(cache(resolve(1)), 1); }); - it('rejects with the rejection reason of the given Future', function (){ + it('rejects with the rejection reason resolve the given Future', function (){ return U.assertRejected(cache(reject(U.error)), U.error); }); @@ -41,7 +41,7 @@ describe('cache()', function (){ }); it('resolves all consumers once a delayed resolution happens', function (){ - var m = cache(after(20, 1)); + var m = cache(after(20)(1)); var a = U.assertResolved(m, 1); var b = U.assertResolved(m, 1); var c = U.assertResolved(m, 1); @@ -81,7 +81,7 @@ describe('cache()', function (){ }, 10); }); - it('does not reset when one of multiple listeners is cancelled', function (done){ + it('does not reset when one resolve multiple listeners is cancelled', function (done){ var m = cache(Future(function (rej, res){ setTimeout(res, 5, 1); return function (){ return done(new Error('Reset happened')) }; @@ -111,14 +111,14 @@ describe('cache()', function (){ it('sets state to Crashed', function (){ var m = cache(Future(U.noop)); m.crash(1); - expect(m._state).to.equal(Cached.Crashed); + expect(m._state).to.equal(Crashed); }); it('does nothing when state is resolved', function (){ var m = cache(Future(U.noop)); m.resolve(1); m.crash(2); - expect(m._state).to.equal(Cached.Resolved); + expect(m._state).to.equal(Resolved); }); }); @@ -129,7 +129,7 @@ describe('cache()', function (){ var m = cache(Future(U.noop)); m.reject(1); m.resolve(2); - expect(m._state).to.equal(Cached.Rejected); + expect(m._state).to.equal(Rejected); }); }); @@ -140,7 +140,7 @@ describe('cache()', function (){ var m = cache(Future(U.noop)); m.resolve(1); m.reject(2); - expect(m._state).to.equal(Cached.Resolved); + expect(m._state).to.equal(Resolved); }); }); @@ -159,7 +159,7 @@ describe('cache()', function (){ describe('#_drainQueue()', function (){ it('is idempotent', function (){ - var m = cache(of(1)); + var m = cache(resolve(1)); m._drainQueue(); m._drainQueue(); m._interpret(U.noop, U.noop, U.noop); @@ -172,7 +172,7 @@ describe('cache()', function (){ describe('#run()', function (){ it('is idempotent', function (){ - var m = cache(of(1)); + var m = cache(resolve(1)); m.run(); m.run(); }); @@ -182,7 +182,7 @@ describe('cache()', function (){ describe('#reset()', function (){ it('is idempotent', function (){ - var m = cache(of(1)); + var m = cache(resolve(1)); m.reset(); m._interpret(U.noop, U.noop, U.noop); m.reset(); @@ -199,9 +199,9 @@ describe('cache()', function (){ describe('#toString()', function (){ - it('returns the code to create the Cached', function (){ - var m = cache(of(1)); - var s = 'cache(Future.of(1))'; + it('returns the code to create the Cache', function (){ + var m = cache(resolve(1)); + var s = 'cache (resolve (1))'; expect(m.toString()).to.equal(s); }); @@ -224,11 +224,11 @@ describe('cache()', function (){ describe('#extractRight()', function (){ it('returns empty array for cold Cacheds', function (){ - expect(cache(of(1)).extractRight()).to.deep.equal([]); + expect(cache(resolve(1)).extractRight()).to.deep.equal([]); }); it('returns array with value for resolved Cacheds', function (){ - var m = cache(of(1)); + var m = cache(resolve(1)); m.run(); expect(m.extractRight()).to.deep.equal([1]); }); diff --git a/test/unit/3.crash.mjs b/test/unit/3.crash.mjs new file mode 100644 index 00000000..bcde5541 --- /dev/null +++ b/test/unit/3.crash.mjs @@ -0,0 +1,17 @@ +import {crash} from '../../src/crash'; +import {eq, assertIsFuture, assertCrashed} from '../util/util'; +import {testFunction, anyArg} from '../util/props'; + +describe('crash()', function (){ + + testFunction('crash', crash, [anyArg], assertIsFuture); + + it('returns a crashed Future', function (){ + return assertCrashed(crash(1), 1); + }); + + it('can be shown as string', function (){ + eq(crash(1).toString(), 'crash (1)'); + }); + +}); diff --git a/test/unit/5.encase-p.mjs b/test/unit/3.encase-p.mjs similarity index 88% rename from test/unit/5.encase-p.mjs rename to test/unit/3.encase-p.mjs index 58e5b9fe..7e4ada08 100644 --- a/test/unit/5.encase-p.mjs +++ b/test/unit/3.encase-p.mjs @@ -14,12 +14,12 @@ describe('encaseP()', function (){ describe('#_interpret()', function (){ it('crashes when the Promise generator throws', function (){ - var m = encaseP(function (){ throw U.error }, 1); + var m = encaseP(function (){ throw U.error })(1); return U.assertCrashed(m, U.error); }); it('crashes when the Promise generator does not return a Promise', function (){ - var m = encaseP(U.noop, 1); + var m = encaseP(U.noop)(1); return U.assertCrashed(m, new TypeError( 'encaseP() expects the function it\'s given to return a Promise/Thenable\n' + ' Actual: undefined\n' + @@ -29,23 +29,23 @@ describe('encaseP()', function (){ }); it('resolves with the resolution value of the returned Promise', function (){ - var actual = encaseP(function (x){ return Promise.resolve(x + 1) }, 1); + var actual = encaseP(function (x){ return Promise.resolve(x + 1) })(1); return U.assertResolved(actual, 2); }); it('rejects with rejection reason of the returned Promise', function (){ - var actual = encaseP(function (){ return Promise.reject(U.error) }, 1); + var actual = encaseP(function (){ return Promise.reject(U.error) })(1); return U.assertRejected(actual, U.error); }); it('ensures no resolution happens after cancel', function (done){ - var actual = encaseP(function (x){ return Promise.resolve(x + 1) }, 1); + var actual = encaseP(function (x){ return Promise.resolve(x + 1) })(1); actual._interpret(done, U.failRej, U.failRes)(); setTimeout(done, 20); }); it('ensures no rejection happens after cancel', function (done){ - var actual = encaseP(function (x){ return Promise.reject(x + 1) }, 1); + var actual = encaseP(function (x){ return Promise.reject(x + 1) })(1); actual._interpret(done, U.failRej, U.failRes)(); setTimeout(done, 20); }); @@ -56,8 +56,8 @@ describe('encaseP()', function (){ it('returns the code to create the Future', function (){ var f = function (a){ return Promise.resolve(a) }; - var m = encaseP(f, null); - expect(m.toString()).to.equal('encaseP(' + f.toString() + ', null)'); + var m = encaseP(f)(null); + expect(m.toString()).to.equal('encaseP (' + f.toString() + ') (null)'); }); }); diff --git a/test/unit/5.encase.mjs b/test/unit/3.encase.mjs similarity index 70% rename from test/unit/5.encase.mjs rename to test/unit/3.encase.mjs index 5adb144a..0c69c003 100644 --- a/test/unit/5.encase.mjs +++ b/test/unit/3.encase.mjs @@ -1,5 +1,5 @@ import chai from 'chai'; -import {encase} from '../../index.mjs'; +import {encase, map} from '../../index.mjs'; import * as U from '../util/util'; import {testFunction, functionArg, anyArg} from '../util/props'; @@ -12,17 +12,17 @@ describe('encase()', function (){ describe('#_interpret()', function (){ it('resolves with the return value of the function', function (){ - var actual = encase(function (x){ return x + 1 }, 1); + var actual = encase(function (x){ return x + 1 })(1); return U.assertResolved(actual, 2); }); it('rejects with the exception thrown by the function', function (){ - var actual = encase(function (a){ throw a, U.error }, 1); + var actual = encase(function (a){ throw a, U.error })(1); return U.assertRejected(actual, U.error); }); it('does not swallow errors from subsequent maps and such', function (){ - var m = encase(function (x){ return x }, 1).map(function (){ throw U.error }); + var m = map(function (){ throw U.error })(encase(function (x){ return x })(1)); return U.assertCrashed(m, U.error); }); @@ -32,8 +32,8 @@ describe('encase()', function (){ it('returns the code to create the Future', function (){ var f = function (a){ return void a }; - var m = encase(f, null); - expect(m.toString()).to.equal('encase(' + f.toString() + ', null)'); + var m = encase(f)(null); + expect(m.toString()).to.equal('encase (' + f.toString() + ') (null)'); }); }); diff --git a/test/unit/6.go.mjs b/test/unit/3.go.mjs similarity index 86% rename from test/unit/6.go.mjs rename to test/unit/3.go.mjs index 87a8e5fa..4b7896f8 100644 --- a/test/unit/6.go.mjs +++ b/test/unit/3.go.mjs @@ -1,5 +1,5 @@ import chai from 'chai'; -import {Future, go, of, after} from '../../index.mjs'; +import {Future, go, resolve, after} from '../../index.mjs'; import * as U from '../util/util'; import * as F from '../util/futures'; import {testFunction, functionArg} from '../util/props'; @@ -41,7 +41,7 @@ describe('go()', function (){ it('crashes when the returned iterator produces something other than a Future', function (){ var m = go(function (){ return {next: function (){ return {done: false, value: null} }} }); return U.assertCrashed(m, new TypeError( - 'go() expects the iterator to produce only valid Futures.\n' + + 'go() expects the value produced by the iterator to be a valid Future.\n' + ' Actual: null :: Null\n' + ' Tip: If you\'re using a generator, make sure you always yield a Future' )); @@ -54,24 +54,24 @@ describe('go()', function (){ it('handles synchronous Futures', function (){ return U.assertResolved(go(function*(){ - var a = yield of(1); - var b = yield of(2); + var a = yield resolve(1); + var b = yield resolve(2); return a + b; }), 3); }); it('handles asynchronous Futures', function (){ return U.assertResolved(go(function*(){ - var a = yield after(10, 1); - var b = yield after(10, 2); + var a = yield after(10)(1); + var b = yield after(10)(2); return a + b; }), 3); }); it('does not mix state over multiple interpretations', function (){ var m = go(function*(){ - var a = yield of(1); - var b = yield after(10, 2); + var a = yield resolve(1); + var b = yield after(10)(2); return a + b; }); return Promise.all([ @@ -83,7 +83,7 @@ describe('go()', function (){ it('is stack safe', function (){ var gen = function*(){ var i = 0; - while(i < U.STACKSIZE + 1){ yield of(i++) } + while(i < U.STACKSIZE + 1){ yield resolve(i++) } return i; }; @@ -93,7 +93,7 @@ describe('go()', function (){ it('cancels the running operation when cancelled', function (done){ var cancel = go(function*(){ - yield of(1); + yield resolve(1); yield Future(function (){ return function (){ return done() } }); })._interpret(done, U.noop, U.noop); cancel(); @@ -106,7 +106,7 @@ describe('go()', function (){ it('returns the code to create the Go', function (){ var f = function*(){}; var m = go(f); - var s = 'go(' + (f.toString()) + ')'; + var s = 'go (' + f.toString() + ')'; expect(m.toString()).to.equal(s); }); diff --git a/test/unit/5.hook.mjs b/test/unit/3.hook.mjs similarity index 54% rename from test/unit/5.hook.mjs rename to test/unit/3.hook.mjs index 37bebf8c..77896a71 100644 --- a/test/unit/5.hook.mjs +++ b/test/unit/3.hook.mjs @@ -1,5 +1,5 @@ import chai from 'chai'; -import {Future, hook, of, reject} from '../../index.mjs'; +import {Future, hook, resolve, reject} from '../../index.mjs'; import * as U from '../util/util'; import * as F from '../util/futures'; import {testFunction, futureArg, functionArg} from '../util/props'; @@ -13,9 +13,9 @@ describe('hook()', function (){ describe('#_interpret()', function (){ it('crashes when the disposal function does not return Future', function (){ - var m = hook(F.resolved, function (){ return 1 }, function (){ return F.resolved }); + var m = hook(F.resolved)(function (){ return 1 })(function (){ return F.resolved }); return U.assertCrashed(m, new TypeError( - 'hook() expects the first function it\'s given to return a Future.\n' + + 'hook() expects the return value from the first function it\'s given to be a valid Future.\n' + ' Actual: 1 :: Number\n' + ' From calling: function (){ return 1 }\n' + ' With: "resolved"' @@ -23,14 +23,14 @@ describe('hook()', function (){ }); it('crashes when the disposal function throws', function (){ - var m = hook(F.resolved, function (){ throw U.error }, function (){ return F.resolved }); + var m = hook(F.resolved)(function (){ throw U.error })(function (){ return F.resolved }); return U.assertCrashed(m, U.error); }); it('crashes when the computation function does not return Future', function (){ - var m = hook(F.resolved, function (){ return F.resolved }, function (){ return 1 }); + var m = hook(F.resolved)(function (){ return F.resolved })(function (){ return 1 }); return U.assertCrashed(m, new TypeError( - 'hook() expects the second function it\'s given to return a Future.\n' + + 'hook() expects the return value from the second function it\'s given to be a valid Future.\n' + ' Actual: 1 :: Number\n' + ' From calling: function (){ return 1 }\n' + ' With: "resolved"' @@ -38,13 +38,13 @@ describe('hook()', function (){ }); it('crashes when the computation function throws', function (){ - var m = hook(F.resolved, function (){ return F.resolved }, function (){ throw U.error }); + var m = hook(F.resolved)(function (){ return F.resolved })(function (){ throw U.error }); return U.assertCrashed(m, U.error); }); it('crashes when the disposal Future rejects', function (){ - var rejected = hook(F.resolved, function (){ return reject(1) }, function (){ return reject(2) }); - var resolved = hook(F.resolved, function (){ return reject(1) }, function (){ return of(2) }); + var rejected = hook(F.resolved)(function (){ return reject(1) })(function (){ return reject(2) }); + var resolved = hook(F.resolved)(function (){ return reject(1) })(function (){ return resolve(2) }); return Promise.all([ U.assertCrashed(rejected, new Error('The disposal Future rejected with 1')), U.assertCrashed(resolved, new Error('The disposal Future rejected with 1')) @@ -53,28 +53,26 @@ describe('hook()', function (){ it('runs the first computation after the second, both with the resource', function (done){ var ran = false; - hook(F.resolved, - function (x){ - expect(x).to.equal('resolved'); - return Future(function (rej, res){ return res(done(ran ? null : new Error('Second did not run'))) }); - }, - function (x){ - expect(x).to.equal('resolved'); - return Future(function (rej, res){ return res(ran = true) }); - } - )._interpret(done, done, U.noop); + hook(F.resolved)(function (x){ + expect(x).to.equal('resolved'); + return Future(function (rej, res){ return res(done(ran ? null : new Error('Second did not run'))) }); + })(function (x){ + expect(x).to.equal('resolved'); + return Future(function (rej, res){ return res(ran = true) }); + })._interpret(done, done, U.noop); }); it('runs the first even if the second rejects', function (done){ - hook(F.resolved, - function (){ return Future(function (){ return done() }) }, - function (){ return reject(2) } - )._interpret(done, U.noop, U.noop); + hook(F.resolved)(function (){ + return Future(function (){ return done() }); + })(function (){ + return reject(2); + })._interpret(done, U.noop, U.noop); }); - it('assumes the state of the second if the first resolves', function (){ - var rejected = hook(F.resolved, function (){ return of(1) }, function (){ return reject(2) }); - var resolved = hook(F.resolved, function (){ return of(1) }, function (){ return of(2) }); + it('assumes the state resolve the second if the first resolves', function (){ + var rejected = hook(F.resolved)(function (){ return resolve(1) })(function (){ return reject(2) }); + var resolved = hook(F.resolved)(function (){ return resolve(1) })(function (){ return resolve(2) }); return Promise.all([ U.assertRejected(rejected, 2), U.assertResolved(resolved, 2) @@ -82,20 +80,20 @@ describe('hook()', function (){ }); it('does not hook after being cancelled', function (done){ - hook(F.resolvedSlow, function (){ return of('dispose') }, U.failRes)._interpret(done, U.failRej, U.failRes)(); + hook(F.resolvedSlow)(function (){ return resolve('dispose') })(U.failRes)._interpret(done, U.failRej, U.failRes)(); setTimeout(done, 25); }); it('does not reject after being cancelled', function (done){ - hook(F.rejectedSlow, function (){ return of('dispose') }, U.failRes)._interpret(done, U.failRej, U.failRes)(); - hook(F.resolved, function (){ return of('dispose') }, function (){ return F.rejectedSlow })._interpret(done, U.failRej, U.failRes)(); + hook(F.rejectedSlow)(function (){ return resolve('dispose') })(U.failRes)._interpret(done, U.failRej, U.failRes)(); + hook(F.resolved)(function (){ return resolve('dispose') })(function (){ return F.rejectedSlow })._interpret(done, U.failRej, U.failRes)(); setTimeout(done, 25); }); it('cancels acquire appropriately', function (done){ var acquire = Future(function (){ return function (){ return done() } }); var cancel = - hook(acquire, function (){ return of('dispose') }, function (){ return of('consume') }) + hook(acquire)(function (){ return resolve('dispose') })(function (){ return resolve('consume') }) ._interpret(done, U.failRej, U.failRes); setTimeout(cancel, 10); }); @@ -103,7 +101,7 @@ describe('hook()', function (){ it('cancels consume appropriately', function (done){ var consume = Future(function (){ return function (){ return done() } }); var cancel = - hook(F.resolved, function (){ return of('dispose') }, function (){ return consume }) + hook(F.resolved)(function (){ return resolve('dispose') })(function (){ return consume }) ._interpret(done, U.failRej, U.failRes); setTimeout(cancel, 10); }); @@ -111,7 +109,7 @@ describe('hook()', function (){ it('cancels delayed consume appropriately', function (done){ var consume = Future(function (){ return function (){ return done() } }); var cancel = - hook(F.resolvedSlow, function (){ return of('dispose') }, function (){ return consume }) + hook(F.resolvedSlow)(function (){ return resolve('dispose') })(function (){ return consume }) ._interpret(done, U.failRej, U.failRes); setTimeout(cancel, 25); }); @@ -119,7 +117,7 @@ describe('hook()', function (){ it('does not cancel disposal', function (done){ var dispose = Future(function (){ return function (){ return done(U.error) } }); var cancel = - hook(F.resolved, function (){ return dispose }, function (){ return of('consume') }) + hook(F.resolved)(function (){ return dispose })(function (){ return resolve('consume') }) ._interpret(done, U.failRej, U.failRes); setTimeout(cancel, 10); setTimeout(done, 50); @@ -128,7 +126,7 @@ describe('hook()', function (){ it('does not cancel delayed dispose', function (done){ var dispose = Future(function (){ return function (){ return done(U.error) } }); var cancel = - hook(F.resolved, function (){ return dispose }, function (){ return F.resolvedSlow }) + hook(F.resolved)(function (){ return dispose })(function (){ return F.resolvedSlow }) ._interpret(done, U.failRej, U.failRes); setTimeout(cancel, 50); setTimeout(done, 100); @@ -136,29 +134,27 @@ describe('hook()', function (){ it('runs the disposal Future when cancelled after acquire', function (done){ var cancel = - hook(F.resolved, function (){ return Future(function (){ done() }) }, function (){ return F.resolvedSlow }) + hook(F.resolved)(function (){ return Future(function (){ done() }) })(function (){ return F.resolvedSlow }) ._interpret(done, U.failRej, U.failRes); setTimeout(cancel, 10); }); - it('does not call the exception handler after having been cancelled', function (done){ - U.raises(done, function (){ - hook(F.resolved, U.K(F.crashedSlow), U.K(F.resolved))._interpret(function (){ - done(new Error('Exception handler called')); - }, U.failRej, U.failRes)(); - }, U.error); - }); + U.itRaises('exceptions that occur after the Future was unsubscribed', function (done){ + hook(F.resolved)(U.K(F.crashedSlow))(U.K(F.resolved))._interpret(function (){ + done(new Error('Exception handler called')); + }, U.failRej, U.failRes)(); + }, U.error); }); describe('#toString()', function (){ it('returns the code which creates the same data-structure', function (){ - var a = of(1); - var d = function (){ return of(2) }; - var c = function (){ return of(3) }; - var m = hook(a, d, c); - var expected = 'hook(' + a.toString() + ', ' + d.toString() + ', ' + c.toString() + ')'; + var a = resolve(1); + var d = function (){ return resolve(2) }; + var c = function (){ return resolve(3) }; + var m = hook(a)(d)(c); + var expected = 'hook (' + a.toString() + ') (' + d.toString() + ') (' + c.toString() + ')'; expect(m.toString()).to.equal(expected); }); diff --git a/test/unit/5.node.mjs b/test/unit/3.node.mjs similarity index 96% rename from test/unit/5.node.mjs rename to test/unit/3.node.mjs index 39f62071..f7a122bd 100644 --- a/test/unit/5.node.mjs +++ b/test/unit/3.node.mjs @@ -52,7 +52,7 @@ describe('node()', function (){ it('returns the code to create the Future', function (){ var f = function (a){ return void a }; var m = node(f); - expect(m.toString()).to.equal('node(' + f.toString() + ')'); + expect(m.toString()).to.equal('node (' + f.toString() + ')'); }); }); diff --git a/test/unit/3.of.mjs b/test/unit/3.of.mjs deleted file mode 100644 index 4ac59eea..00000000 --- a/test/unit/3.of.mjs +++ /dev/null @@ -1,41 +0,0 @@ -import chai from 'chai'; -import {FL} from '../../src/internal/const'; -import {Future, of} from '../../index.mjs'; -import * as U from '../util/util'; -import {testFunction, anyArg} from '../util/props'; - -var expect = chai.expect; - -describe('of()', function (){ - - it('is also available as fantasy-land function', function (){ - expect(of).to.equal(Future[FL.of]); - }); - - testFunction('of', of, [anyArg], U.assertValidFuture); - - describe('#_interpret()', function (){ - - it('calls success callback with the value', function (){ - return U.assertResolved(of(1), 1); - }); - - }); - - describe('#extractRight()', function (){ - - it('returns array with the value', function (){ - expect(of(1).extractRight()).to.deep.equal([1]); - }); - - }); - - describe('#toString()', function (){ - - it('returns the code to create the Resolved', function (){ - expect(of(1).toString()).to.equal('Future.of(1)'); - }); - - }); - -}); diff --git a/test/unit/5.parallel.mjs b/test/unit/3.parallel.mjs similarity index 69% rename from test/unit/5.parallel.mjs rename to test/unit/3.parallel.mjs index 7fb27960..09304faa 100644 --- a/test/unit/5.parallel.mjs +++ b/test/unit/3.parallel.mjs @@ -1,5 +1,5 @@ import chai from 'chai'; -import {Future, parallel, of, reject, after} from '../../index.mjs'; +import {Future, parallel, resolve, reject, after} from '../../index.mjs'; import * as U from '../util/util'; import * as F from '../util/futures'; import {testFunction, positiveIntegerArg, futureArrayArg} from '../util/props'; @@ -12,28 +12,28 @@ describe('parallel()', function (){ describe('#_interpret()', function (){ - it('crashes when one of the Futures crash', function (){ - return U.assertCrashed(parallel(2, [F.resolved, F.crashed]), U.error); + it('crashes when one resolve the Futures crash', function (){ + return U.assertCrashed(parallel(2)([F.resolved, F.crashed]), U.error); }); - it('crashes when one of the Futures crash', function (){ - return U.assertCrashed(parallel(2, [F.resolved, F.resolved, F.resolved, F.resolved, F.resolved, F.crashed]), U.error); + it('crashes when one resolve the Futures crash', function (){ + return U.assertCrashed(parallel(2)([F.resolved, F.resolved, F.resolved, F.resolved, F.resolved, F.crashed]), U.error); }); it('throws when the Array contains something other than Futures', function (){ var xs = [NaN, {}, [], 1, 'a', new Date, undefined, null]; - var fs = xs.map(function (x){ return function (){ return parallel(1, [x])._interpret(U.noop, U.noop, U.noop) } }); + var fs = xs.map(function (x){ return function (){ return parallel(1)([x])._interpret(U.noop, U.noop, U.noop) } }); fs.forEach(function (f){ return expect(f).to.throw(TypeError, /Future/) }); }); it('parallelizes execution', function (){ this.timeout(70); - var actual = parallel(5, [ - after(20, 'a'), - after(20, 'b'), - after(20, 'c'), - after(20, 'd'), - after(20, 'e') + var actual = parallel(5)([ + after(20)('a'), + after(20)('b'), + after(20)('c'), + after(20)('d'), + after(20)('e') ]); return U.assertResolved(actual, ['a', 'b', 'c', 'd', 'e']); }); @@ -48,24 +48,24 @@ describe('parallel()', function (){ res('a'); }, 20); }); - var actual = parallel(2, U.repeat(8, m)); + var actual = parallel(2)(U.repeat(8, m)); return U.assertResolved(actual, U.repeat(8, 'a')); }); it('runs all in parallel when given number larger than the array length', function (){ this.timeout(70); - var actual = parallel(10, [ - after(20, 'a'), - after(20, 'b'), - after(20, 'c'), - after(20, 'd'), - after(20, 'e') + var actual = parallel(10)([ + after(20)('a'), + after(20)('b'), + after(20)('c'), + after(20)('d'), + after(20)('e') ]); return U.assertResolved(actual, ['a', 'b', 'c', 'd', 'e']); }); it('can deal with synchronously resolving futures', function (done){ - parallel(5, U.repeat(10, of(1)))._interpret(done, U.failRej, function (xs){ + parallel(5)(U.repeat(10, resolve(1)))._interpret(done, U.failRej, function (xs){ expect(xs).to.have.length(10); done(); }); @@ -80,7 +80,7 @@ describe('parallel()', function (){ res(i); }); }); - parallel(5, ms)._interpret(done, U.noop, function (out){ + parallel(5)(ms)._interpret(done, U.noop, function (out){ expect(out).to.deep.equal(ns); expect(xs).to.deep.equal(ns); done(); @@ -96,7 +96,7 @@ describe('parallel()', function (){ setTimeout(res, 10, i); }); }); - parallel(5, ms)._interpret(done, U.noop, function (out){ + parallel(5)(ms)._interpret(done, U.noop, function (out){ expect(out).to.deep.equal(ns); expect(xs).to.deep.equal(ns); done(); @@ -104,34 +104,34 @@ describe('parallel()', function (){ }); it('resolves to an empty array when given an empty array', function (){ - return U.assertResolved(parallel(1, []), []); + return U.assertResolved(parallel(1)([]), []); }); it('runs all in parallel when given Infinity', function (){ this.timeout(70); - var actual = parallel(Infinity, [ - after(20, 'a'), - after(20, 'b'), - after(20, 'c'), - after(20, 'd'), - after(20, 'e') + var actual = parallel(Infinity)([ + after(20)('a'), + after(20)('b'), + after(20)('c'), + after(20)('d'), + after(20)('e') ]); return U.assertResolved(actual, ['a', 'b', 'c', 'd', 'e']); }); - it('rejects if one of the input rejects', function (){ - var actual = parallel(2, [F.resolved, reject('err')]); + it('rejects if one resolve the input rejects', function (){ + var actual = parallel(2)([F.resolved, reject('err')]); return U.assertRejected(actual, 'err'); }); it('does not reject multiple times', function (done){ - var actual = parallel(2, [F.rejectedSlow, F.rejected]); + var actual = parallel(2)([F.rejectedSlow, F.rejected]); actual._interpret(done, function (){ return done() }, U.failRes); }); it('cancels Futures when cancelled', function (done){ var m = Future(function (){ return function (){ return done() } }); - var cancel = parallel(1, [m])._interpret(done, U.noop, U.noop); + var cancel = parallel(1)([m])._interpret(done, U.noop, U.noop); setTimeout(cancel, 20); }); @@ -145,7 +145,7 @@ describe('parallel()', function (){ clearTimeout(x); }; }); - var cancel = parallel(2, [m, m, m, m])._interpret(done, U.failRej, U.failRes); + var cancel = parallel(2)([m, m, m, m])._interpret(done, U.failRej, U.failRes); setTimeout(function (){ cancel(); expect(i).to.equal(2); @@ -156,13 +156,13 @@ describe('parallel()', function (){ it('does not interpret any computations after one rejects', function (done){ var m = Future(function (){ done(U.error) }); - parallel(2, [F.rejected, m])._interpret(done, U.noop, U.failRes); + parallel(2)([F.rejected, m])._interpret(done, U.noop, U.failRes); done(); }); it('automatically cancels running computations when one rejects', function (done){ var m = Future(function (){ return function (){ done() } }); - parallel(2, [m, F.rejected])._interpret(done, U.noop, U.failRes); + parallel(2)([m, F.rejected])._interpret(done, U.noop, U.failRes); }); it('does not cancel settled computations (#123)', function (done){ @@ -179,27 +179,27 @@ describe('parallel()', function (){ return function (){ return done(U.error) }; }; - parallel(2, [m1, m2])._interpret(done, U.noop, U.noop); + parallel(2)([m1, m2])._interpret(done, U.noop, U.noop); setTimeout(done, 50, null); }); it('does not resolve after being cancelled', function (done){ - var cancel = parallel(1, [F.resolvedSlow, F.resolvedSlow]) + var cancel = parallel(1)([F.resolvedSlow, F.resolvedSlow]) ._interpret(done, U.failRej, U.failRes); setTimeout(cancel, 10); setTimeout(done, 50); }); it('does not reject after being cancelled', function (done){ - var cancel = parallel(1, [F.rejectedSlow, F.rejectedSlow]) + var cancel = parallel(1)([F.rejectedSlow, F.rejectedSlow]) ._interpret(done, U.failRej, U.failRes); setTimeout(cancel, 10); setTimeout(done, 50); }); it('is stack safe (#130)', function (done){ - var ms = Array.from({length: U.STACKSIZE}, function (_, i){ return of(i) }); - parallel(1, ms)._interpret(done, U.failRej, function (xs){ + var ms = Array.from({length: U.STACKSIZE}, function (_, i){ return resolve(i) }); + parallel(1)(ms)._interpret(done, U.failRej, function (xs){ expect(xs).to.have.length(U.STACKSIZE); done(); }); @@ -210,9 +210,12 @@ describe('parallel()', function (){ describe('#toString()', function (){ it('returns the code to create the Parallel', function (){ - var m = parallel(Infinity, [of(1), of(2)]); - var s = 'parallel(2, [Future.of(1), Future.of(2)])'; - expect(m.toString()).to.equal(s); + var m1 = parallel(Infinity)([resolve(1), resolve(2)]); + var m2 = parallel(2)([resolve(1), resolve(2)]); + var s1 = 'parallel (Infinity) ([resolve (1), resolve (2)])'; + var s2 = 'parallel (2) ([resolve (1), resolve (2)])'; + expect(m1.toString()).to.equal(s1); + expect(m2.toString()).to.equal(s2); }); }); diff --git a/test/unit/3.reject-after.mjs b/test/unit/3.reject-after.mjs index a0880c43..67f1e020 100644 --- a/test/unit/3.reject-after.mjs +++ b/test/unit/3.reject-after.mjs @@ -1,67 +1,36 @@ -import chai from 'chai'; -import {Future, rejectAfter, never} from '../../index.mjs'; -import * as U from '../util/util'; +import {rejectAfter, never} from '../../index.mjs'; +import {eq, assertValidFuture, assertRejected, failRej, failRes} from '../util/util'; import {testFunction, positiveIntegerArg, anyArg} from '../util/props'; -var expect = chai.expect; - describe('rejectAfter()', function (){ - testFunction('rejectAfter', rejectAfter, [positiveIntegerArg, anyArg], U.assertValidFuture); + testFunction('rejectAfter', rejectAfter, [positiveIntegerArg, anyArg], assertValidFuture); it('returns Never when given Infinity', function (){ - expect(rejectAfter(Infinity, 1)).to.equal(never); + eq(rejectAfter(Infinity)(1), never); }); describe('#_interpret()', function (){ - - it('calls failure callback with the reason', function (){ - return U.assertRejected(rejectAfter(20, 1), 1); + it('rejects with the given value', function (){ + return assertRejected(rejectAfter(20)(1), 1); }); it('clears its internal timeout when cancelled', function (done){ - rejectAfter(20, 1)._interpret(done, U.failRej, U.failRes)(); + rejectAfter(20)(1)._interpret(done, failRej, failRes)(); setTimeout(done, 25); }); - - }); - - describe('#race()', function (){ - - it('races undeterministic Futures the conventional way', function (){ - var m = rejectAfter(1, 1); - var undeterministic = Future(function (){}); - var actual = m.race(undeterministic); - expect(actual).to.not.equal(m); - expect(actual).to.not.equal(undeterministic); - return U.assertRejected(actual, 1); - }); - - }); - - describe('#swap()', function (){ - - it('returns a resolved Future', function (){ - var m = rejectAfter(10, 1); - return U.assertResolved(m.swap(), 1); - }); - }); describe('#extractLeft()', function (){ - - it('returns array with the reason', function (){ - expect(rejectAfter(20, 1).extractLeft()).to.deep.equal([1]); + it('returns array with the value', function (){ + eq(rejectAfter(20)(1).extractLeft(), [1]); }); - }); describe('#toString()', function (){ - - it('returns the code to create the RejectAfter', function (){ - expect(rejectAfter(20, 1).toString()).to.equal('rejectAfter(20, 1)'); + it('returns the code to create the After', function (){ + eq(rejectAfter(20)(1).toString(), 'rejectAfter (20) (1)'); }); - }); }); diff --git a/test/unit/3.reject.mjs b/test/unit/3.reject.mjs index 5d82870c..79312776 100644 --- a/test/unit/3.reject.mjs +++ b/test/unit/3.reject.mjs @@ -1,40 +1,21 @@ -import chai from 'chai'; -import {Future, reject} from '../../index.mjs'; -import * as U from '../util/util'; +import {reject} from '../../'; +import {eq, assertValidFuture, assertRejected} from '../util/util'; import {testFunction, anyArg} from '../util/props'; -var expect = chai.expect; - describe('reject()', function (){ - testFunction('reject', reject, [anyArg], U.assertValidFuture); + testFunction('reject', reject, [anyArg], assertValidFuture); - it('returns an instance of Future', function (){ - expect(reject(1)).to.be.an.instanceof(Future); + it('returns a rejected Future', function (){ + return assertRejected(reject(1), 1); }); - describe('#_interpret()', function (){ - - it('calls failure callback with the reason', function (){ - return U.assertRejected(reject(1), 1); - }); - + it('provides its reason to extractLeft()', function (){ + eq(reject(1).extractLeft(), [1]); }); - describe('#extractLeft()', function (){ - - it('returns array with the reason', function (){ - expect(reject(1).extractLeft()).to.deep.equal([1]); - }); - - }); - - describe('#toString()', function (){ - - it('returns the code to create the Rejected', function (){ - expect(reject(1).toString()).to.equal('reject(1)'); - }); - + it('can be shown as string', function (){ + eq(reject(1).toString(), 'reject (1)'); }); }); diff --git a/test/unit/3.resolve.mjs b/test/unit/3.resolve.mjs new file mode 100644 index 00000000..cd2cfe58 --- /dev/null +++ b/test/unit/3.resolve.mjs @@ -0,0 +1,21 @@ +import {resolve} from '../../'; +import {eq, assertValidFuture, assertResolved} from '../util/util'; +import {testFunction, anyArg} from '../util/props'; + +describe('resolve()', function (){ + + testFunction('resolve', resolve, [anyArg], assertValidFuture); + + it('returns a resolved Future', function (){ + return assertResolved(resolve(1), 1); + }); + + it('provides its reason to extractRight()', function (){ + eq(resolve(1).extractRight(), [1]); + }); + + it('can be shown as string', function (){ + eq(resolve(1).toString(), 'resolve (1)'); + }); + +}); diff --git a/test/unit/4.alt.mjs b/test/unit/4.alt.mjs new file mode 100644 index 00000000..80e97b91 --- /dev/null +++ b/test/unit/4.alt.mjs @@ -0,0 +1,43 @@ +import Either from 'sanctuary-either'; +import {Future, alt} from '../../index.mjs'; +import {assertCrashed, eq, assertValidFuture, noop, assertResolved, assertRejected, error} from '../util/util'; +import {crashed, rejected, resolved, rejectedSlow, resolvedSlow} from '../util/futures'; +import {testFunction, altArg, futureArg} from '../util/props'; + +describe('alt()', function (){ + + testFunction('alt', alt, [altArg, futureArg], assertValidFuture); + + it('chooses the resolved over the rejected Future', function (){ + return Promise.all([ + assertResolved(alt(crashed)(resolved), 'resolved'), + assertResolved(alt(resolved)(resolved), 'resolved'), + assertResolved(alt(rejected)(resolved), 'resolved'), + assertResolved(alt(resolved)(rejected), 'resolved'), + assertRejected(alt(rejected)(rejected), 'rejected'), + assertResolved(alt(resolved)(resolvedSlow), 'resolvedSlow'), + assertResolved(alt(resolvedSlow)(resolved), 'resolved'), + assertRejected(alt(rejected)(rejectedSlow), 'rejected'), + assertRejected(alt(rejectedSlow)(rejected), 'rejectedSlow'), + assertCrashed(alt(rejected)(crashed), error), + assertCrashed(alt(resolved)(crashed), error), + assertCrashed(alt(crashed)(rejected), error), + ]); + }); + + it('dispatches to Fantasy Land alt', function (){ + eq(alt(Either.Right(42))(Either.Left(42)), Either.Right(42)); + }); + + it('cancels the running Future', function (done){ + var m = Future(function (){ return function (){ return done() } }); + var cancel = alt(m)(m)._interpret(done, noop, noop); + cancel(); + }); + + it('displays correctly as string', function (){ + eq(alt(rejected)(resolved).toString(), 'alt (reject ("rejected")) (resolve ("resolved"))'); + }); + +}); + diff --git a/test/unit/4.and.mjs b/test/unit/4.and.mjs new file mode 100644 index 00000000..4b19c914 --- /dev/null +++ b/test/unit/4.and.mjs @@ -0,0 +1,37 @@ +import {Future, and} from '../../index.mjs'; +import {assertCrashed, eq, assertValidFuture, noop, assertResolved, assertRejected, error} from '../util/util'; +import {crashed, rejected, resolved, rejectedSlow, resolvedSlow} from '../util/futures'; +import {testFunction, futureArg} from '../util/props'; + +describe('and()', function (){ + + testFunction('and', and, [futureArg, futureArg], assertValidFuture); + + it('chooses the rejected over the resolved Future', function (){ + return Promise.all([ + assertResolved(and(resolved)(resolved), 'resolved'), + assertRejected(and(rejected)(resolved), 'rejected'), + assertRejected(and(resolved)(rejected), 'rejected'), + assertRejected(and(rejected)(rejected), 'rejected'), + assertResolved(and(resolved)(resolvedSlow), 'resolved'), + assertResolved(and(resolvedSlow)(resolved), 'resolvedSlow'), + assertRejected(and(rejected)(rejectedSlow), 'rejectedSlow'), + assertRejected(and(rejectedSlow)(rejected), 'rejected'), + assertCrashed(and(resolved)(crashed), error), + assertCrashed(and(rejected)(crashed), error), + assertCrashed(and(crashed)(resolved), error), + ]); + }); + + it('cancels the running Future', function (done){ + var m = Future(function (){ return function (){ return done() } }); + var cancel = and(m)(m)._interpret(done, noop, noop); + cancel(); + }); + + it('displays correctly as string', function (){ + eq(and(rejected)(resolved).toString(), 'and (reject ("rejected")) (resolve ("resolved"))'); + }); + +}); + diff --git a/test/unit/4.ap.mjs b/test/unit/4.ap.mjs new file mode 100644 index 00000000..ad2cbd7d --- /dev/null +++ b/test/unit/4.ap.mjs @@ -0,0 +1,50 @@ +import Either from 'sanctuary-either'; +import {Future, ap, resolve, reject, after} from '../../index.mjs'; +import {assertCrashed, assertRejected, assertResolved, assertValidFuture, noop, add, eq, bang} from '../util/util'; +import {testFunction, applyArg, futureArg} from '../util/props'; + +describe('ap()', function (){ + + testFunction('ap', ap, [applyArg, futureArg], assertValidFuture); + + it('dispatches to Fantasy Land ap', function (){ + eq(ap(Either.Right('hello'))(Either.Right(bang)), Either.Right('hello!')); + }); + + it('crashes when the other does not resolve to a Function', function (){ + var m = ap(resolve(1))(resolve(null)); + return assertCrashed(m, new TypeError( + 'ap expects the second Future to resolve to a Function\n' + + ' Actual: null' + )); + }); + + it('applies the Function on the right to the value on the left', function (){ + return Promise.all([ + assertResolved(ap(resolve(1))(resolve(add(1))), 2), + assertRejected(ap(resolve(add(1)))(reject('err')), 'err'), + assertRejected(ap(reject('err'))(resolve(add(1))), 'err'), + assertResolved(ap(after(20)(1))(resolve(add(1))), 2), + assertResolved(ap(resolve(1))(after(20)(add(1))), 2) + ]); + }); + + it('cancels the left Future if cancel is called while it is running', function (done){ + var left = Future(function (){ return function (){ return done() } }); + var right = resolve(add(1)); + var cancel = ap(left)(right)._interpret(done, noop, noop); + cancel(); + }); + + it('cancels the right Future if cancel is called while it is running', function (done){ + var left = resolve(1); + var right = Future(function (){ return function (){ return done() } }); + var cancel = ap(left)(right)._interpret(done, noop, noop); + cancel(); + }); + + it('displays correctly as string', function (){ + eq(ap(resolve(1))(resolve(2)).toString(), 'ap (resolve (1)) (resolve (2))'); + }); + +}); diff --git a/test/unit/4.bimap.mjs b/test/unit/4.bimap.mjs new file mode 100644 index 00000000..dd298694 --- /dev/null +++ b/test/unit/4.bimap.mjs @@ -0,0 +1,28 @@ +import Either from 'sanctuary-either'; +import {bimap} from '../../index.mjs'; +import {assertCrashed, assertRejected, assertResolved, assertValidFuture, I, bang, eq, throwing, error} from '../util/util'; +import {testFunction, functionArg, bifunctorArg} from '../util/props'; +import {resolved, rejected} from '../util/futures'; + +describe('bimap()', function (){ + + testFunction('bimap', bimap, [functionArg, functionArg, bifunctorArg], assertValidFuture); + + it('runs a bimap transformation on Futures', function (){ + return Promise.all([ + assertRejected(bimap(bang)(I)(rejected), 'rejected!'), + assertResolved(bimap(I)(bang)(resolved), 'resolved!'), + assertCrashed(bimap(throwing)(I)(rejected), error), + assertCrashed(bimap(I)(throwing)(resolved), error), + ]); + }); + + it('dispatches to Fantasy Land bimap otherwise', function (){ + eq(bimap(I)(bang)(Either.Right('hello')), Either.Right('hello!')); + }); + + it('displays correctly as string', function (){ + eq(bimap(I)(bang)(resolved).toString(), 'bimap (' + I.toString() + ') (' + bang.toString() + ') (resolve ("resolved"))'); + }); + +}); diff --git a/test/unit/4.both.mjs b/test/unit/4.both.mjs new file mode 100644 index 00000000..03e4a75b --- /dev/null +++ b/test/unit/4.both.mjs @@ -0,0 +1,65 @@ +import {Future, both, node, done} from '../../index.mjs'; +import {assertCrashed, assertRejected, assertResolved, assertValidFuture, error, noop, eq} from '../util/util'; +import {crashed, rejected, resolved, crashedSlow, rejectedSlow, resolvedSlow} from '../util/futures'; +import {testFunction, futureArg} from '../util/props'; + +describe('both()', function (){ + + testFunction('both', both, [futureArg, futureArg], assertValidFuture); + + it('resolves to a tuple of both resolution values', function (){ + return Promise.all([ + assertCrashed(both(crashed)(crashed), error), + assertCrashed(both(rejected)(crashed), error), + assertCrashed(both(crashed)(resolved), error), + assertCrashed(both(resolved)(crashed), error), + + assertRejected(both(rejected)(rejected), 'rejected'), + assertRejected(both(rejected)(rejectedSlow), 'rejected'), + assertRejected(both(rejectedSlow)(rejected), 'rejected'), + assertRejected(both(crashed)(rejected), 'rejected'), + assertRejected(both(rejected)(crashedSlow), 'rejected'), + assertRejected(both(resolved)(rejected), 'rejected'), + assertRejected(both(rejected)(resolved), 'rejected'), + + assertResolved(both(resolved)(resolved), ['resolved', 'resolved']), + assertResolved(both(resolved)(resolvedSlow), ['resolved', 'resolvedSlow']), + assertResolved(both(resolvedSlow)(resolved), ['resolvedSlow', 'resolved']), + assertResolved(both(resolvedSlow)(resolvedSlow), ['resolvedSlow', 'resolvedSlow']), + ]); + }); + + it('[GH #118] does not call the left computation twice', function (cb){ + var called = false; + var left = node(function (f){ return called ? cb(error) : setTimeout(f, 20, null, called = true) }); + return done(cb)(both(left)(resolvedSlow)); + }); + + it('[GH #118] does not call the right computation twice', function (cb){ + var called = false; + var right = node(function (f){ return called ? cb(error) : setTimeout(f, 20, null, called = true) }); + return done(cb)(both(resolvedSlow)(right)); + }); + + it('cancels the right if the left rejects', function (done){ + var m = both(rejectedSlow)(Future(function (){ return function (){ return done() } })); + m._interpret(done, noop, noop); + }); + + it('cancels the left if the right rejects', function (done){ + var m = both(Future(function (){ return function (){ return done() } }))(rejectedSlow); + m._interpret(done, noop, noop); + }); + + it('creates a cancel function which cancels both Futures', function (done){ + var cancelled = false; + var m = Future(function (){ return function (){ return (cancelled ? done() : (cancelled = true)) } }); + var cancel = both(m)(m)._interpret(done, noop, noop); + cancel(); + }); + + it('displays correctly as string', function (){ + eq(both(rejected)(resolved).toString(), 'both (reject ("rejected")) (resolve ("resolved"))'); + }); + +}); diff --git a/test/unit/4.chain-rec.mjs b/test/unit/4.chain-rec.mjs deleted file mode 100644 index c4040657..00000000 --- a/test/unit/4.chain-rec.mjs +++ /dev/null @@ -1,80 +0,0 @@ -import chai from 'chai'; -import {Future, of, after, reject} from '../../index.mjs'; -import {isIteration} from '../../src/internal/iteration'; -import * as U from '../util/util'; - -var expect = chai.expect; - -describe('chainRec()', function (){ - - it('is a binary function', function (){ - expect(Future.chainRec).to.be.a('function'); - expect(Future.chainRec.length).to.equal(2); - }); - - describe('#_interpret()', function (){ - - it('crashes if the iterator throws', function (){ - var m = Future.chainRec(function (){ throw U.error }); - return U.assertCrashed(m, U.error); - }); - - it('does not break if the iteration does not contain a value key', function (){ - var actual = Future.chainRec(function (f, g, x){ return (x, of({done: true})) }, 0); - return U.assertResolved(actual, undefined); - }); - - it('calls the function with Next, Done and the initial value', function (){ - Future.chainRec(function (next, done, x){ - expect(next).to.be.a('function'); - expect(next.length).to.equal(1); - expect(next(x)).to.satisfy(isIteration); - expect(done).to.be.a('function'); - expect(done.length).to.equal(1); - expect(done(x)).to.satisfy(isIteration); - expect(x).to.equal(42); - return of(done(x)); - }, 42)._interpret(U.noop, U.noop, U.noop); - }); - - it('calls the function with the value from the current iteration', function (){ - var i = 0; - Future.chainRec(function (f, g, x){ - expect(x).to.equal(i); - return x < 5 ? of(f(++i)) : of(g(x)); - }, i)._interpret(U.noop, U.noop, U.noop); - }); - - it('works asynchronously', function (){ - var actual = Future.chainRec(function (f, g, x){ return after(10, x < 5 ? f(x + 1) : g(x)) }, 0); - return U.assertResolved(actual, 5); - }); - - it('responds to failure', function (){ - var m = Future.chainRec(function (f, g, x){ return reject(x) }, 1); - return U.assertRejected(m, 1); - }); - - it('responds to failure after chaining async', function (){ - var m = Future.chainRec( - function (f, g, x){ return x < 2 ? after(10, f(x + 1)) : reject(x) }, 0 - ); - return U.assertRejected(m, 2); - }); - - it('can be cancelled straight away', function (done){ - Future.chainRec(function (f, g, x){ return after(10, g(x)) }, 1) - ._interpret(done, U.failRej, U.failRes)(); - setTimeout(done, 20); - }); - - it('can be cancelled after some iterations', function (done){ - var m = Future.chainRec(function (f, g, x){ return after(10, x < 5 ? f(x + 1) : g(x)) }, 0); - var cancel = m._interpret(done, U.failRej, U.failRes); - setTimeout(cancel, 25); - setTimeout(done, 70); - }); - - }); - -}); diff --git a/test/unit/4.chain-rej.mjs b/test/unit/4.chain-rej.mjs new file mode 100644 index 00000000..d7881de4 --- /dev/null +++ b/test/unit/4.chain-rej.mjs @@ -0,0 +1,32 @@ +import {chainRej, resolve, reject} from '../../index.mjs'; +import {assertCrashed, assertRejected, assertResolved, assertValidFuture, bang, throwing, error, eq} from '../util/util'; +import {rejected, resolved, rejectedSlow} from '../util/futures'; +import {testFunction, functionArg, futureArg} from '../util/props'; + +describe('chainRej()', function (){ + + testFunction('chainRej', chainRej, [functionArg, futureArg], assertValidFuture); + + it('crashes when the given function does not return Future', function (){ + return assertCrashed(chainRej(bang)(rejected), new TypeError( + 'chainRej expects the return value from the function it\'s given to be a valid Future.\n' + + ' Actual: "rejected!" :: String\n' + + ' When called with: "rejected"' + )); + }); + + it('calls the function with the rejection reason and sequences the returned Future', function (){ + return Promise.all([ + assertResolved(chainRej(resolve)(rejected), 'rejected'), + assertResolved(chainRej(resolve)(rejectedSlow), 'rejectedSlow'), + assertResolved(chainRej(reject)(resolved), 'resolved'), + assertRejected(chainRej(reject)(rejected), 'rejected'), + assertCrashed(chainRej(throwing)(rejected), error) + ]); + }); + + it('displays correctly as string', function (){ + eq(chainRej(resolve)(resolved).toString(), 'chainRej (' + resolve.toString() + ') (resolve ("resolved"))'); + }); + +}); diff --git a/test/unit/4.chain.mjs b/test/unit/4.chain.mjs new file mode 100644 index 00000000..5a3fc461 --- /dev/null +++ b/test/unit/4.chain.mjs @@ -0,0 +1,37 @@ +import Either from 'sanctuary-either'; +import {chain, resolve, reject} from '../../index.mjs'; +import {assertCrashed, assertRejected, assertResolved, assertValidFuture, bang, eq, throwing, error} from '../util/util'; +import {rejected, resolved, resolvedSlow} from '../util/futures'; +import {testFunction, functionArg, chainArg} from '../util/props'; + +describe('chain()', function (){ + + testFunction('chain', chain, [functionArg, chainArg], assertValidFuture); + + it('dispatches to Fantasy Land chain', function (){ + eq(chain(Either.Left)(Either.Right(42)), Either.Left(42)); + }); + + it('crashes when the given function does not return Future', function (){ + return assertCrashed(chain(bang)(resolved), new TypeError( + 'chain expects the return value from the function it\'s given to be a valid Future.\n' + + ' Actual: "resolved!" :: String\n' + + ' When called with: "resolved"' + )); + }); + + it('calls the function with the resolution value and sequences the returned Future', function (){ + return Promise.all([ + assertRejected(chain(reject)(resolved), 'resolved'), + assertRejected(chain(reject)(resolvedSlow), 'resolvedSlow'), + assertResolved(chain(resolve)(resolved), 'resolved'), + assertRejected(chain(resolve)(rejected), 'rejected'), + assertCrashed(chain(throwing)(resolved), error) + ]); + }); + + it('displays correctly as string', function (){ + eq(chain(resolve)(resolved).toString(), 'chain (' + resolve.toString() + ') (resolve ("resolved"))'); + }); + +}); diff --git a/test/unit/4.fold.mjs b/test/unit/4.fold.mjs new file mode 100644 index 00000000..1468b43c --- /dev/null +++ b/test/unit/4.fold.mjs @@ -0,0 +1,23 @@ +import {fold} from '../../index.mjs'; +import {assertCrashed, assertResolved, assertValidFuture, bang, B, throwing, error, eq} from '../util/util'; +import {resolved, rejected} from '../util/futures'; +import {testFunction, functionArg, futureArg} from '../util/props'; + +describe('fold()', function (){ + + testFunction('fold', fold, [functionArg, functionArg, futureArg], assertValidFuture); + + it('joins the rejection and resolution values into the resolution branch', function (){ + return Promise.all([ + assertResolved(fold(bang)(B(bang)(bang))(rejected), 'rejected!'), + assertResolved(fold(bang)(B(bang)(bang))(resolved), 'resolved!!'), + assertCrashed(fold(throwing)(B)(rejected), error), + assertCrashed(fold(B)(throwing)(resolved), error), + ]); + }); + + it('displays correctly as string', function (){ + eq(fold(bang)(B)(resolved).toString(), 'fold (' + bang.toString() + ') (' + B.toString() + ') (resolve ("resolved"))'); + }); + +}); diff --git a/test/unit/4.lastly.mjs b/test/unit/4.lastly.mjs new file mode 100644 index 00000000..6eeef3e0 --- /dev/null +++ b/test/unit/4.lastly.mjs @@ -0,0 +1,49 @@ +import {lastly, resolve, reject, map} from '../../index.mjs'; +import {assertRejected, assertResolved, assertValidFuture, failRej, failRes, noop, eq} from '../util/util'; +import {rejected, rejectedSlow, resolved, resolvedSlow} from '../util/futures'; +import {testFunction, futureArg} from '../util/props'; + +describe('lastly()', function (){ + + testFunction('lastly', lastly, [futureArg, futureArg], assertValidFuture); + + it('runs the second Future when the first resolves', function (done){ + lastly(map(done)(resolve(null)))(resolve(1))._interpret(done, noop, noop); + }); + + it('runs the second Future when the first rejects', function (done){ + lastly(map(done)(resolve(null)))(reject(1))._interpret(done, noop, noop); + }); + + it('resolves with the resolution value of the first', function (){ + var actual = lastly(resolve(2))(resolve(1)); + return assertResolved(actual, 1); + }); + + it('rejects with the rejection reason of the first if the second resolves', function (){ + var actual = lastly(resolve(2))(reject(1)); + return assertRejected(actual, 1); + }); + + it('always rejects with the rejection reason of the second', function (){ + var actualResolved = lastly(reject(2))(resolve(1)); + var actualRejected = lastly(reject(2))(reject(1)); + return Promise.all([ + assertRejected(actualResolved, 2), + assertRejected(actualRejected, 2) + ]); + }); + + it('does nothing after being cancelled', function (done){ + lastly(resolved)(resolvedSlow)._interpret(done, failRej, failRes)(); + lastly(resolvedSlow)(resolved)._interpret(done, failRej, failRes)(); + lastly(rejected)(rejectedSlow)._interpret(done, failRej, failRes)(); + lastly(rejectedSlow)(rejected)._interpret(done, failRej, failRes)(); + setTimeout(done, 25); + }); + + it('displays correctly as string', function (){ + eq(lastly(rejected)(resolved).toString(), 'lastly (reject ("rejected")) (resolve ("resolved"))'); + }); + +}); diff --git a/test/unit/4.map-rej.mjs b/test/unit/4.map-rej.mjs new file mode 100644 index 00000000..740c617c --- /dev/null +++ b/test/unit/4.map-rej.mjs @@ -0,0 +1,32 @@ +import {mapRej} from '../../index.mjs'; +import {assertCrashed, assertRejected, assertResolved, assertValidFuture, bang, failRej, failRes, throwing, error, eq} from '../util/util'; +import {rejected, resolved, resolvedSlow, rejectedSlow} from '../util/futures'; +import {testFunction, functionArg, futureArg} from '../util/props'; + +describe('mapRej()', function (){ + + testFunction('mapRej', mapRej, [functionArg, futureArg], assertValidFuture); + + it('maps the rejection branch with the given function', function (){ + return Promise.all([ + assertRejected(mapRej(bang)(rejected), 'rejected!'), + assertResolved(mapRej(bang)(resolved), 'resolved'), + assertCrashed(mapRej(throwing)(rejected), error) + ]); + }); + + it('does not resolve after being cancelled', function (done){ + mapRej(failRej)(resolvedSlow)._interpret(done, failRej, failRes)(); + setTimeout(done, 25); + }); + + it('does not reject after being cancelled', function (done){ + mapRej(failRej)(rejectedSlow)._interpret(done, failRej, failRes)(); + setTimeout(done, 25); + }); + + it('displays correctly as string', function (){ + eq(mapRej(bang)(rejected).toString(), 'mapRej (' + bang.toString() + ') (reject ("rejected"))'); + }); + +}); diff --git a/test/unit/4.map.mjs b/test/unit/4.map.mjs new file mode 100644 index 00000000..a6464f40 --- /dev/null +++ b/test/unit/4.map.mjs @@ -0,0 +1,37 @@ +import Either from 'sanctuary-either'; +import {map} from '../../index.mjs'; +import {assertCrashed, assertRejected, assertResolved, assertValidFuture, bang, failRej, failRes, eq, throwing, error} from '../util/util'; +import {rejected, resolved, resolvedSlow, rejectedSlow} from '../util/futures'; +import {testFunction, functionArg, functorArg} from '../util/props'; + +describe('map()', function (){ + + testFunction('map', map, [functionArg, functorArg], assertValidFuture); + + it('dispatches to Fantasy Land map', function (){ + eq(map(bang)(Either.Right('hello')), Either.Right('hello!')); + }); + + it('maps the resolution branch with the given function', function (){ + return Promise.all([ + assertRejected(map(bang)(rejected), 'rejected'), + assertResolved(map(bang)(resolved), 'resolved!'), + assertCrashed(map(throwing)(resolved), error) + ]); + }); + + it('does not resolve after being cancelled', function (done){ + map(failRej)(resolvedSlow)._interpret(done, failRej, failRes)(); + setTimeout(done, 25); + }); + + it('does not reject after being cancelled', function (done){ + map(failRej)(rejectedSlow)._interpret(done, failRej, failRes)(); + setTimeout(done, 25); + }); + + it('displays correctly as string', function (){ + eq(map(bang)(resolved).toString(), 'map (' + bang.toString() + ') (resolve ("resolved"))'); + }); + +}); diff --git a/test/unit/4.parallel-ap.mjs b/test/unit/4.parallel-ap.mjs new file mode 100644 index 00000000..1a1f2f39 --- /dev/null +++ b/test/unit/4.parallel-ap.mjs @@ -0,0 +1,42 @@ +import {Future, resolve, reject, after} from '../../index.mjs'; +import {parallelAp} from '../../src/parallel-ap'; +import {assertCrashed, assertRejected, assertResolved, assertValidFuture, noop, add} from '../util/util'; +import {testFunction, futureArg} from '../util/props'; + +describe('parallelAp()', function (){ + + testFunction('parallelAp', parallelAp, [futureArg, futureArg], assertValidFuture); + + it('crashes when the other does not resolve to a Function', function (){ + var m = parallelAp(resolve(1))(resolve(null)); + return assertCrashed(m, new TypeError( + 'parallelAp expects the second Future to resolve to a Function\n' + + ' Actual: null' + )); + }); + + it('applies the Function on the right to the value on the left', function (){ + return Promise.all([ + assertResolved(parallelAp(resolve(1))(resolve(add(1))), 2), + assertRejected(parallelAp(resolve(add(1)))(reject('err')), 'err'), + assertRejected(parallelAp(reject('err'))(resolve(add(1))), 'err'), + assertResolved(parallelAp(after(20)(1))(resolve(add(1))), 2), + assertResolved(parallelAp(resolve(1))(after(20)(add(1))), 2) + ]); + }); + + it('cancels the left Future if cancel is called while it is running', function (done){ + var left = Future(function (){ return function (){ return done() } }); + var right = resolve(add(1)); + var cancel = parallelAp(left)(right)._interpret(done, noop, noop); + cancel(); + }); + + it('cancels the right Future if cancel is called while it is running', function (done){ + var left = resolve(1); + var right = Future(function (){ return function (){ return done() } }); + var cancel = parallelAp(left)(right)._interpret(done, noop, noop); + cancel(); + }); + +}); diff --git a/test/unit/4.race.mjs b/test/unit/4.race.mjs new file mode 100644 index 00000000..c2116179 --- /dev/null +++ b/test/unit/4.race.mjs @@ -0,0 +1,62 @@ +import {Future, race} from '../../index.mjs'; +import {assertCrashed, assertRejected, assertResolved, assertValidFuture, error, noop, eq} from '../util/util'; +import {crashed, crashedSlow, rejected, rejectedSlow, resolved, resolvedSlow} from '../util/futures'; +import {testFunction, futureArg} from '../util/props'; + +describe('race()', function (){ + + testFunction('race', race, [futureArg, futureArg], assertValidFuture); + + it('races one Future against another', function (){ + return Promise.all([ + assertCrashed(race(crashed)(resolvedSlow), error), + assertResolved(race(crashedSlow)(resolved), 'resolved'), + assertCrashed(race(crashed)(rejectedSlow), error), + assertRejected(race(crashedSlow)(rejected), 'rejected'), + assertResolved(race(resolved)(crashedSlow), 'resolved'), + assertCrashed(race(resolvedSlow)(crashed), error), + assertRejected(race(rejected)(crashedSlow), 'rejected'), + assertCrashed(race(rejectedSlow)(crashed), error), + assertResolved(race(resolved)(resolvedSlow), 'resolved'), + assertResolved(race(resolvedSlow)(resolved), 'resolved'), + assertRejected(race(rejectedSlow)(rejected), 'rejected'), + assertRejected(race(rejected)(rejectedSlow), 'rejected'), + assertResolved(race(rejectedSlow)(resolved), 'resolved'), + assertRejected(race(rejected)(resolvedSlow), 'rejected'), + assertResolved(race(resolved)(rejectedSlow), 'resolved'), + assertRejected(race(resolvedSlow)(rejected), 'rejected') + ]); + }); + + it('cancels the right if the left resolves', function (done){ + var m = race(resolvedSlow)(Future(function (){ return function (){ return done() } })); + m._interpret(done, noop, noop); + }); + + it('cancels the left if the right resolves', function (done){ + var m = race(Future(function (){ return function (){ return done() } }))(resolvedSlow); + m._interpret(done, noop, noop); + }); + + it('cancels the right if the left rejects', function (done){ + var m = race(rejectedSlow)(Future(function (){ return function (){ return done() } })); + m._interpret(done, noop, noop); + }); + + it('cancels the left if the right rejects', function (done){ + var m = race(Future(function (){ return function (){ return done() } }))(rejectedSlow); + m._interpret(done, noop, noop); + }); + + it('creates a cancel function which cancels both Futures', function (done){ + var cancelled = false; + var m = Future(function (){ return function (){ return (cancelled ? done() : (cancelled = true)) } }); + var cancel = race(m)(m)._interpret(done, noop, noop); + cancel(); + }); + + it('displays correctly as string', function (){ + eq(race(rejected)(resolved).toString(), 'race (reject ("rejected")) (resolve ("resolved"))'); + }); + +}); diff --git a/test/unit/4.swap.mjs b/test/unit/4.swap.mjs new file mode 100644 index 00000000..ce205120 --- /dev/null +++ b/test/unit/4.swap.mjs @@ -0,0 +1,23 @@ +import {swap, resolve, reject} from '../../index.mjs'; +import {assertRejected, assertResolved, assertValidFuture, eq} from '../util/util'; +import {testFunction, futureArg} from '../util/props'; + +describe('swap()', function (){ + + testFunction('swap', swap, [futureArg], assertValidFuture); + + it('rejects with the resolution value', function (){ + var actual = swap(resolve(1)); + return assertRejected(actual, 1); + }); + + it('resolves with the rejection reason', function (){ + var actual = swap(reject(1)); + return assertResolved(actual, 1); + }); + + it('displays correctly as string', function (){ + eq(swap(resolve(42)).toString(), 'swap (resolve (42))'); + }); + +}); diff --git a/test/unit/5.alt.mjs b/test/unit/5.alt.mjs deleted file mode 100644 index ab1b8e3f..00000000 --- a/test/unit/5.alt.mjs +++ /dev/null @@ -1,96 +0,0 @@ -import chai from 'chai'; -import {Future, alt, of, reject, after, or} from '../../index.mjs'; -import * as U from '../util/util'; -import * as F from '../util/futures'; -import {testFunction, altArg} from '../util/props'; - -var expect = chai.expect; - -describe('alt()', function (){ - - testFunction('alt', alt, [altArg, altArg], U.assertValidFuture); - - it('is an alias of "or"', function (){ - U.eq(alt, or); - }); - - it('allows for the implementation of `any` in terms of reduce', function (){ - var any = function (ms){ return ms.reduce(alt, reject('empty list')) }; - return Promise.all([ - U.assertRejected(any([]), 'empty list'), - U.assertRejected(any([reject(1)]), 1), - U.assertResolved(any([reject(1), of(2)]), 2), - U.assertResolved(any([reject(1), after(20, 2), of(3)]), 2) - ]); - }); - - describe('#_interpret()', function (){ - - describe('(res, res)', function (){ - - it('resolves with left if left resolves first', function (){ - return U.assertResolved(alt(F.resolved, F.resolvedSlow), 'resolved'); - }); - - it('resolves with left if left resolves last', function (){ - return U.assertResolved(alt(F.resolvedSlow, F.resolved), 'resolvedSlow'); - }); - - }); - - describe('(rej, rej)', function (){ - - it('rejects with right if right rejects first', function (){ - return U.assertRejected(alt(F.rejectedSlow, F.rejected), 'rejected'); - }); - - it('rejects with right if right rejects last', function (){ - return U.assertRejected(alt(F.rejected, F.rejectedSlow), 'rejectedSlow'); - }); - - }); - - describe('(rej, res)', function (){ - - it('resolves with right if right resolves first', function (){ - return U.assertResolved(alt(F.rejectedSlow, F.resolved), 'resolved'); - }); - - it('resolves with right if right resolves last', function (){ - return U.assertResolved(alt(F.rejected, F.resolvedSlow), 'resolvedSlow'); - }); - - }); - - describe('(res, rej)', function (){ - - it('resolves with left if left resolves first', function (){ - return U.assertResolved(alt(F.resolved, F.rejectedSlow), 'resolved'); - }); - - it('resolves with left if left resolves last', function (){ - return U.assertResolved(alt(F.resolvedSlow, F.rejected), 'resolvedSlow'); - }); - - }); - - it('cancels the running Future', function (done){ - var m = Future(function (){ return function (){ return done() } }); - var cancel = alt(m, m)._interpret(done, U.noop, U.noop); - cancel(); - }); - - }); - - describe('#toString()', function (){ - - it('returns the code to create the data-structure', function (){ - var m = Future(function (){ return function (){} }); - var actual = alt(m, m).toString(); - expect(actual).to.equal(((m.toString()) + '.alt(' + (m.toString()) + ')')); - }); - - }); - -}); - diff --git a/test/unit/5.and.mjs b/test/unit/5.and.mjs deleted file mode 100644 index d70e8cee..00000000 --- a/test/unit/5.and.mjs +++ /dev/null @@ -1,94 +0,0 @@ -import chai from 'chai'; -import {Future, and, of} from '../../index.mjs'; -import * as U from '../util/util'; -import * as F from '../util/futures'; -import {testFunction, futureArg} from '../util/props'; - -var expect = chai.expect; - -describe('and()', function (){ - - testFunction('and', and, [futureArg, futureArg], U.assertValidFuture); - - it('allows for the implementation of `all` in terms of reduce', function (){ - var all = function (ms){ return ms.reduce(and, of(true)) }; - return Promise.all([ - U.assertResolved(all([]), true), - U.assertRejected(all([F.rejected, F.resolved]), 'rejected'), - U.assertRejected(all([F.resolved, F.rejected]), 'rejected'), - U.assertResolved(all([F.resolvedSlow, F.resolved]), 'resolved'), - U.assertResolved(all([F.resolved, F.resolvedSlow]), 'resolvedSlow'), - U.assertRejected(all([F.rejected, F.rejectedSlow]), 'rejected'), - U.assertRejected(all([F.rejectedSlow, F.rejected]), 'rejectedSlow') - ]); - }); - - describe('#_interpret()', function (){ - - describe('(res, res)', function (){ - - it('resolves with right if left resolves first', function (){ - return U.assertResolved(and(F.resolved, F.resolvedSlow), 'resolvedSlow'); - }); - - it('resolves with right if left resolves last', function (){ - return U.assertResolved(and(F.resolvedSlow, F.resolved), 'resolved'); - }); - - }); - - describe('(rej, rej)', function (){ - - it('rejects with left if right rejects first', function (){ - return U.assertRejected(and(F.rejectedSlow, F.rejected), 'rejectedSlow'); - }); - - it('rejects with left if right rejects last', function (){ - return U.assertRejected(and(F.rejected, F.rejectedSlow), 'rejected'); - }); - - }); - - describe('(rej, res)', function (){ - - it('rejects with left if right resolves first', function (){ - return U.assertRejected(and(F.rejectedSlow, F.resolved), 'rejectedSlow'); - }); - - it('rejects with left if right resolves last', function (){ - return U.assertRejected(and(F.rejected, F.resolvedSlow), 'rejected'); - }); - - }); - - describe('(res, rej)', function (){ - - it('rejects with right if left resolves first', function (){ - return U.assertRejected(and(F.resolved, F.rejectedSlow), 'rejectedSlow'); - }); - - it('rejects with right if left resolves last', function (){ - return U.assertRejected(and(F.resolvedSlow, F.rejected), 'rejected'); - }); - - }); - - it('cancels the running Future', function (done){ - var m = Future(function (){ return function (){ return done() } }); - var cancel = and(m, m)._interpret(done, U.noop, U.noop); - cancel(); - }); - - }); - - describe('#toString()', function (){ - - it('returns the code to create the data-structure', function (){ - var m = Future(function (){ return function (){} }); - var actual = and(m, m).toString(); - expect(actual).to.equal(((m.toString()) + '.and(' + (m.toString()) + ')')); - }); - - }); - -}); diff --git a/test/unit/5.ap.mjs b/test/unit/5.ap.mjs deleted file mode 100644 index 7f945c1e..00000000 --- a/test/unit/5.ap.mjs +++ /dev/null @@ -1,69 +0,0 @@ -import chai from 'chai'; -import {Future, ap, of, reject, after} from '../../index.mjs'; -import * as U from '../util/util'; -import {testFunction, applyArg} from '../util/props'; - -var expect = chai.expect; - -describe('ap()', function (){ - - testFunction('ap', ap, [applyArg, applyArg], U.assertValidFuture); - - describe('#_interpret()', function (){ - - it('crashes when the other does not resolve to a Function', function (){ - var m = ap(of(null), of(1)); - return U.assertCrashed(m, new TypeError( - 'Future#ap() expects its first argument to be a Future of a Function\n' + - ' Actual: Future.of(null)' - )); - }); - - it('calls the function contained in the given Future to its contained value', function (){ - var actual = ap(of(U.add(1)), of(1)); - return U.assertResolved(actual, 2); - }); - - it('rejects if one of the two reject', function (){ - var left = ap(reject('err'), of(U.add(1))); - var right = ap(of(U.add(1)), reject('err')); - return Promise.all([ - U.assertRejected(left, 'err'), - U.assertRejected(right, 'err') - ]); - }); - - it('does not matter if the left resolves late', function (){ - var actual = ap(of(U.add(1)), after(20, 1)); - return U.assertResolved(actual, 2); - }); - - it('does not matter if the right resolves late', function (){ - var actual = ap(after(20, U.add(1)), of(1)); - return U.assertResolved(actual, 2); - }); - - it('interprets in sequence', function (done){ - var running = true; - var right = after(20, 1).map(function (x){ running = false; return x }); - var left = of(function (){ expect(running).to.equal(false); done() }); - ap(left, right)._interpret(done, U.noop, U.noop); - }); - - it('cancels the left Future if cancel is called while it is running', function (done){ - var left = of(U.add(1)); - var right = Future(function (){ return function (){ return done() } }); - var cancel = ap(left, right)._interpret(done, U.noop, U.noop); - cancel(); - }); - - it('cancels the right Future if cancel is called while it is running', function (done){ - var left = Future(function (){ return function (){ return done() } }); - var right = of(1); - var cancel = ap(left, right)._interpret(done, U.noop, U.noop); - cancel(); - }); - - }); - -}); diff --git a/test/unit/5.bimap.mjs b/test/unit/5.bimap.mjs deleted file mode 100644 index 6c0fb96a..00000000 --- a/test/unit/5.bimap.mjs +++ /dev/null @@ -1,23 +0,0 @@ -import {reject, bimap, of} from '../../index.mjs'; -import * as U from '../util/util'; -import {testFunction, functionArg, bifunctorArg} from '../util/props'; - -describe('bimap()', function (){ - - testFunction('bimap', bimap, [functionArg, functionArg, bifunctorArg], U.assertValidFuture); - - describe('#_interpret()', function (){ - - it('applies the first function to the value in the rejection branch', function (){ - var actual = bimap(U.add(1), U.failRes, reject(1)); - return U.assertRejected(actual, 2); - }); - - it('applies the second function to the value in the resolution branch', function (){ - var actual = bimap(U.failRej, U.add(1), of(1)); - return U.assertResolved(actual, 2); - }); - - }); - -}); diff --git a/test/unit/5.both.mjs b/test/unit/5.both.mjs deleted file mode 100644 index 61a164c3..00000000 --- a/test/unit/5.both.mjs +++ /dev/null @@ -1,141 +0,0 @@ -import {Future, both, node} from '../../index.mjs'; -import * as U from '../util/util'; -import * as F from '../util/futures'; -import {testFunction, futureArg} from '../util/props'; - -describe('both()', function (){ - - testFunction('both', both, [futureArg, futureArg], U.assertValidFuture); - - describe('#_interpret()', function (){ - - describe('(Crashed, Resolved)', function (){ - - it('crashes if left settles first', function (){ - return U.assertCrashed(both(F.crashed, F.resolvedSlow), new Error( - 'Intentional error for unit testing' - )); - }); - - it('crashes if left settles last', function (){ - return U.assertCrashed(both(F.crashedSlow, F.resolved), U.error); - }); - - }); - - describe('(Crashed, Rejected)', function (){ - - it('crashes if left settles first', function (){ - return U.assertCrashed(both(F.crashed, F.rejectedSlow), U.error); - }); - - it('rejects if left settles last', function (){ - return U.assertRejected(both(F.crashedSlow, F.rejected), 'rejected'); - }); - - }); - - describe('(Resolved, Crashed)', function (){ - - it('crashes if left settles first', function (){ - return U.assertCrashed(both(F.resolved, F.crashedSlow), U.error); - }); - - it('crashes if left settles last', function (){ - return U.assertCrashed(both(F.resolvedSlow, F.crashed), U.error); - }); - - }); - - describe('(Rejected, Crashed)', function (){ - - it('rejects if left settles first', function (){ - return U.assertRejected(both(F.rejected, F.crashedSlow), 'rejected'); - }); - - it('crashes if left settles last', function (){ - return U.assertCrashed(both(F.rejectedSlow, F.crashed), U.error); - }); - - }); - - describe('(Resolved, Resolved)', function (){ - - it('resolves with both if left settles first', function (){ - return U.assertResolved(both(F.resolved, F.resolvedSlow), ['resolved', 'resolvedSlow']); - }); - - it('resolves with both if left settles last', function (){ - return U.assertResolved(both(F.resolvedSlow, F.resolved), ['resolvedSlow', 'resolved']); - }); - - }); - - describe('(Rejected, Rejected)', function (){ - - it('rejects with right if right rejects first', function (){ - return U.assertRejected(both(F.rejectedSlow, F.rejected), 'rejected'); - }); - - it('rejects with left if right rejects last', function (){ - return U.assertRejected(both(F.rejected, F.rejectedSlow), 'rejected'); - }); - - }); - - describe('(Rejected, Resolved)', function (){ - - it('rejects with left if right settles first', function (){ - return U.assertRejected(both(F.rejectedSlow, F.resolved), 'rejectedSlow'); - }); - - it('rejects with left if right settles last', function (){ - return U.assertRejected(both(F.rejected, F.resolvedSlow), 'rejected'); - }); - - }); - - describe('(Resolved, Rejected)', function (){ - - it('rejects with right if left settles first', function (){ - return U.assertRejected(both(F.resolved, F.rejectedSlow), 'rejectedSlow'); - }); - - it('rejects with right if left settles last', function (){ - return U.assertRejected(both(F.resolvedSlow, F.rejected), 'rejected'); - }); - - }); - - it('[GH #118] does not call the left computation twice', function (done){ - var called = false; - var left = node(function (f){ return called ? done(U.error) : setTimeout(f, 20, null, called = true) }); - return both(left, F.resolvedSlow).done(done); - }); - - it('[GH #118] does not call the right computation twice', function (done){ - var called = false; - var right = node(function (f){ return called ? done(U.error) : setTimeout(f, 20, null, called = true) }); - return both(F.resolvedSlow, right).done(done); - }); - - it('cancels the right if the left rejects', function (done){ - var m = both(F.rejectedSlow, Future(function (){ return function (){ return done() } })); - m._interpret(done, U.noop, U.noop); - }); - - it('cancels the left if the right rejects', function (done){ - var m = both(Future(function (){ return function (){ return done() } }), F.rejectedSlow); - m._interpret(done, U.noop, U.noop); - }); - - it('creates a cancel function which cancels both Futures', function (done){ - var cancelled = false; - var m = Future(function (){ return function (){ return (cancelled ? done() : (cancelled = true)) } }); - var cancel = both(m, m)._interpret(done, U.noop, U.noop); - cancel(); - }); - - }); - -}); diff --git a/test/unit/5.chain-rec.mjs b/test/unit/5.chain-rec.mjs new file mode 100644 index 00000000..45fc8bd2 --- /dev/null +++ b/test/unit/5.chain-rec.mjs @@ -0,0 +1,81 @@ +import chai from 'chai'; +import {resolve, after, reject} from '../../index.mjs'; +import {chainRec} from '../../src/chain-rec'; +import {isIteration} from '../../src/internal/iteration'; +import {assertCrashed, assertRejected, assertResolved, error, failRej, failRes, noop} from '../util/util'; + +var expect = chai.expect; + +describe('chainRec()', function (){ + + it('is a binary function', function (){ + expect(chainRec).to.be.a('function'); + expect(chainRec.length).to.equal(2); + }); + + describe('#_interpret()', function (){ + + it('crashes if the iterator throws', function (){ + var m = chainRec(function (){ throw error }); + return assertCrashed(m, error); + }); + + it('does not break if the iteration does not contain a value key', function (){ + var actual = chainRec(function (f, g, x){ return (x, resolve({done: true})) }, 0); + return assertResolved(actual, undefined); + }); + + it('calls the function with Next, Done and the initial value', function (){ + chainRec(function (next, done, x){ + expect(next).to.be.a('function'); + expect(next.length).to.equal(1); + expect(next(x)).to.satisfy(isIteration); + expect(done).to.be.a('function'); + expect(done.length).to.equal(1); + expect(done(x)).to.satisfy(isIteration); + expect(x).to.equal(42); + return resolve(done(x)); + }, 42)._interpret(noop, noop, noop); + }); + + it('calls the function with the value from the current iteration', function (){ + var i = 0; + chainRec(function (f, g, x){ + expect(x).to.equal(i); + return x < 5 ? resolve(f(++i)) : resolve(g(x)); + }, i)._interpret(noop, noop, noop); + }); + + it('works asynchronously', function (){ + var actual = chainRec(function (f, g, x){ return after(10)(x < 5 ? f(x + 1) : g(x)) }, 0); + return assertResolved(actual, 5); + }); + + it('responds to failure', function (){ + var m = chainRec(function (f, g, x){ return reject(x) }, 1); + return assertRejected(m, 1); + }); + + it('responds to failure after chaining async', function (){ + var m = chainRec( + function (f, g, x){ return x < 2 ? after(10)(f(x + 1)) : reject(x) }, 0 + ); + return assertRejected(m, 2); + }); + + it('can be cancelled straight away', function (done){ + chainRec(function (f, g, x){ return after(10)(g(x)) }, 1) + ._interpret(done, failRej, failRes)(); + setTimeout(done, 20); + }); + + it('can be cancelled after some iterations', function (done){ + var m = chainRec(function (f, g, x){ return after(10)(x < 5 ? f(x + 1) : g(x)) }, 0); + var cancel = m._interpret(done, failRej, failRes); + setTimeout(cancel, 25); + setTimeout(done, 70); + }); + + }); + +}); diff --git a/test/unit/5.chain-rej.mjs b/test/unit/5.chain-rej.mjs deleted file mode 100644 index b7289a48..00000000 --- a/test/unit/5.chain-rej.mjs +++ /dev/null @@ -1,55 +0,0 @@ -import chai from 'chai'; -import {chainRej, of} from '../../index.mjs'; -import * as U from '../util/util'; -import * as F from '../util/futures'; -import {testFunction, functionArg, futureArg} from '../util/props'; - -var expect = chai.expect; - -describe('chainRej()', function (){ - - testFunction('chainRej', chainRej, [functionArg, futureArg], U.assertValidFuture); - - describe('#_interpret()', function (){ - - it('crashes when the given function does not return Future', function (){ - var m = chainRej(function (){ return null }, F.rejected); - return U.assertCrashed(m, new TypeError( - 'Future#chainRej() expects the function it\'s given to return a Future.\n' + - ' Actual: null :: Null\n' + - ' From calling: function (){ return null }\n' + - ' With: "rejected"' - )); - }); - - it('calls the given function with the inner of the Future', function (done){ - chainRej(function (x){ - expect(x).to.equal('rejected'); - done(); - return of(null); - }, F.rejected)._interpret(done, U.noop, U.noop); - }); - - it('returns a Future with an inner equal to the returned Future', function (){ - var actual = chainRej(function (){ return F.resolved }, F.rejected); - return U.assertResolved(actual, 'resolved'); - }); - - it('maintains resolved state', function (){ - var actual = chainRej(function (){ return F.resolvedSlow }, F.resolved); - return U.assertResolved(actual, 'resolved'); - }); - - it('assumes rejected state', function (){ - var actual = chainRej(function (){ return F.rejectedSlow }, F.rejected); - return U.assertRejected(actual, 'rejectedSlow'); - }); - - it('does not chain after being cancelled', function (done){ - chainRej(U.failRej, F.rejectedSlow)._interpret(done, U.failRej, U.failRes)(); - setTimeout(done, 25); - }); - - }); - -}); diff --git a/test/unit/5.chain.mjs b/test/unit/5.chain.mjs deleted file mode 100644 index 103aad64..00000000 --- a/test/unit/5.chain.mjs +++ /dev/null @@ -1,61 +0,0 @@ -import chai from 'chai'; -import {chain, of} from '../../index.mjs'; -import * as U from '../util/util'; -import * as F from '../util/futures'; -import {testFunction, functionArg, chainArg} from '../util/props'; - -var expect = chai.expect; - -describe('chain()', function (){ - - testFunction('chain', chain, [functionArg, chainArg], U.assertValidFuture); - - describe('#_interpret()', function (){ - - it('throws TypeError when the given function does not return Future', function (){ - var m = chain(function (){ return null }, F.resolved); - return U.assertCrashed(m, new TypeError( - 'Future#chain() expects the function it\'s given to return a Future.\n' + - ' Actual: null :: Null\n' + - ' From calling: function (){ return null }\n' + - ' With: "resolved"' - )); - }); - - it('calls the given function with the inner of the Future', function (done){ - chain(function (x){ - expect(x).to.equal('resolved'); - done(); - return of(null); - }, F.resolved)._interpret(done, U.noop, U.noop); - }); - - it('returns a Future with an inner equal to the returned Future', function (){ - var actual = chain(function (){ return F.resolvedSlow }, F.resolved); - return U.assertResolved(actual, 'resolvedSlow'); - }); - - it('maintains rejected state', function (){ - var actual = chain(function (){ return F.resolved }, F.rejected); - return U.assertRejected(actual, 'rejected'); - }); - - it('assumes rejected state', function (){ - var actual = chain(function (){ return F.rejected }, F.resolved); - return U.assertRejected(actual, 'rejected'); - }); - - it('does not chain after being cancelled', function (done){ - chain(U.failRes, F.resolvedSlow)._interpret(done, U.failRej, U.failRes)(); - setTimeout(done, 25); - }); - - it('does not reject after being cancelled', function (done){ - chain(U.failRes, F.rejectedSlow)._interpret(done, U.failRej, U.failRes)(); - chain(function (){ return F.rejectedSlow }, F.resolved)._interpret(done, U.failRej, U.failRes)(); - setTimeout(done, 25); - }); - - }); - -}); diff --git a/test/unit/5.encase-n.mjs b/test/unit/5.encase-n.mjs deleted file mode 100644 index 80d745b4..00000000 --- a/test/unit/5.encase-n.mjs +++ /dev/null @@ -1,60 +0,0 @@ -import chai from 'chai'; -import {encaseN} from '../../index.mjs'; -import * as U from '../util/util'; -import {testFunction, functionArg, anyArg} from '../util/props'; - -var expect = chai.expect; - -describe('encaseN()', function (){ - - testFunction('encaseN', encaseN, [functionArg, anyArg], U.assertValidFuture); - - describe('#_interpret()', function (){ - - it('crashes when the function throws', function (){ - var m = encaseN(function (){ throw U.error }, 1); - return U.assertCrashed(m, U.error); - }); - - it('rejects when the callback is called with (err)', function (){ - var f = function (a, done){ return done(U.error) }; - return U.assertRejected(encaseN(f, 'a'), U.error); - }); - - it('resolves when the callback is called with (null, a)', function (){ - var f = function (a, done){ return done(null, a) }; - return U.assertResolved(encaseN(f, 'a'), 'a'); - }); - - it('settles with the last synchronous call to done', function (){ - var f = function (a, done){ done(null, 'a'); done(U.error); done(null, a) }; - return U.assertResolved(encaseN(f, 'b'), 'b'); - }); - - it('settles with the first asynchronous call to done', function (){ - var f = function (a, done){ - setTimeout(done, 10, null, a); - setTimeout(done, 50, null, 'b'); - }; - return U.assertResolved(encaseN(f, 'a'), 'a'); - }); - - it('ensures no continuations are called after cancel', function (done){ - var f = function (a, done){ return setTimeout(done, 5) }; - encaseN(f, 'a')._interpret(done, U.failRej, U.failRes)(); - setTimeout(done, 20); - }); - - }); - - describe('#toString()', function (){ - - it('returns the code to create the EncaseN', function (){ - var f = function (a, f){ return void f }; - var m = encaseN(f, null); - expect(m.toString()).to.equal('encaseN(' + f.toString() + ', null)'); - }); - - }); - -}); diff --git a/test/unit/5.encase-n2.mjs b/test/unit/5.encase-n2.mjs deleted file mode 100644 index a64ccb36..00000000 --- a/test/unit/5.encase-n2.mjs +++ /dev/null @@ -1,60 +0,0 @@ -import chai from 'chai'; -import {encaseN2} from '../../index.mjs'; -import * as U from '../util/util'; -import {testFunction, functionArg, anyArg} from '../util/props'; - -var expect = chai.expect; - -describe('encaseN2()', function (){ - - testFunction('encaseN2', encaseN2, [functionArg, anyArg, anyArg], U.assertValidFuture); - - describe('#_interpret()', function (){ - - it('crashes when the function throws', function (){ - var m = encaseN2(function (){ throw U.error }, 1, 1); - return U.assertCrashed(m, U.error); - }); - - it('rejects when the callback is called with (err)', function (){ - var f = function (a, b, done){ return done(U.error) }; - return U.assertRejected(encaseN2(f, 'a', 'b'), U.error); - }); - - it('resolves when the callback is called with (null, a)', function (){ - var f = function (a, b, done){ return done(null, a) }; - return U.assertResolved(encaseN2(f, 'a', 'b'), 'a'); - }); - - it('settles with the last synchronous call to done', function (){ - var f = function (a, b, done){ done(null, a); done(U.error); done(null, b) }; - return U.assertResolved(encaseN2(f, 'a', 'b'), 'b'); - }); - - it('settles with the first asynchronous call to done', function (){ - var f = function (a, b, done){ - setTimeout(done, 10, null, a); - setTimeout(done, 50, null, b); - }; - return U.assertResolved(encaseN2(f, 'a', 'b'), 'a'); - }); - - it('ensures no continuations are called after cancel', function (done){ - var f = function (a, b, done){ return setTimeout(done, 5) }; - encaseN2(f, 'a', 'b')._interpret(done, U.failRej, U.failRes)(); - setTimeout(done, 20); - }); - - }); - - describe('#toString()', function (){ - - it('returns the code to create the EncaseN', function (){ - var f = function (a, b, f){ return void f }; - var m = encaseN2(f, null, null); - expect(m.toString()).to.equal('encaseN2(' + f.toString() + ', null, null)'); - }); - - }); - -}); diff --git a/test/unit/5.encase-n3.mjs b/test/unit/5.encase-n3.mjs deleted file mode 100644 index a7afede7..00000000 --- a/test/unit/5.encase-n3.mjs +++ /dev/null @@ -1,60 +0,0 @@ -import chai from 'chai'; -import {encaseN3} from '../../index.mjs'; -import * as U from '../util/util'; -import {testFunction, functionArg, anyArg} from '../util/props'; - -var expect = chai.expect; - -describe('encaseN3()', function (){ - - testFunction('encaseN3', encaseN3, [functionArg, anyArg, anyArg, anyArg], U.assertValidFuture); - - describe('#_interpret()', function (){ - - it('crashes when the function throws', function (){ - var m = encaseN3(function (){ throw U.error }, 1, 1, 1); - return U.assertCrashed(m, U.error); - }); - - it('rejects when the callback is called with (err)', function (){ - var f = function (a, b, c, done){ return done(U.error) }; - return U.assertRejected(encaseN3(f, 'a', 'b', 'c'), U.error); - }); - - it('resolves when the callback is called with (null, a)', function (){ - var f = function (a, b, c, done){ return done(null, a) }; - return U.assertResolved(encaseN3(f, 'a', 'b', 'c'), 'a'); - }); - - it('settles with the last synchronous call to done', function (){ - var f = function (a, b, c, done){ done(null, a); done(U.error); done(null, c) }; - return U.assertResolved(encaseN3(f, 'a', 'b', 'c'), 'c'); - }); - - it('settles with the first asynchronous call to done', function (){ - var f = function (a, b, c, done){ - setTimeout(done, 10, null, a); - setTimeout(done, 50, null, c); - }; - return U.assertResolved(encaseN3(f, 'a', 'b', 'c'), 'a'); - }); - - it('ensures no continuations are called after cancel', function (done){ - var f = function (a, b, c, done){ return setTimeout(done, 5) }; - encaseN3(f, 'a', 'b', 'c')._interpret(done, U.failRej, U.failRes)(); - setTimeout(done, 20); - }); - - }); - - describe('#toString()', function (){ - - it('returns the code to create the EncaseN', function (){ - var f = function (a, b, c, f){ return void f }; - var m = encaseN3(f, null, null, null); - expect(m.toString()).to.equal('encaseN3(' + f.toString() + ', null, null, null)'); - }); - - }); - -}); diff --git a/test/unit/5.encase-p2.mjs b/test/unit/5.encase-p2.mjs deleted file mode 100644 index 2e9ac666..00000000 --- a/test/unit/5.encase-p2.mjs +++ /dev/null @@ -1,66 +0,0 @@ -/* eslint prefer-promise-reject-errors: 0 */ - -import chai from 'chai'; -import {encaseP2} from '../../index.mjs'; -import * as U from '../util/util'; -import {testFunction, functionArg, anyArg} from '../util/props'; - -var expect = chai.expect; - -describe('encaseP2()', function (){ - - testFunction('encaseP2', encaseP2, [functionArg, anyArg, anyArg], U.assertValidFuture); - - describe('#_interpret()', function (){ - - it('crashes when the Promise generator throws', function (){ - var m = encaseP2(function (){ throw U.error }, 1, 1); - return U.assertCrashed(m, U.error); - }); - - it('crashes when the Promise generator does not return a Promise', function (){ - var m = encaseP2(U.noop, 1, 1); - return U.assertCrashed(m, new TypeError( - 'encaseP2() expects the function it\'s given to return a Promise/Thenable\n' + - ' Actual: undefined\n' + - ' From calling: function (){}\n' + - ' With 1: 1\n' + - ' With 2: 1' - )); - }); - - it('resolves with the resolution value of the returned Promise', function (){ - var actual = encaseP2(function (x, y){ return Promise.resolve(y + 1) }, 1, 1); - return U.assertResolved(actual, 2); - }); - - it('rejects with rejection reason of the returned Promise', function (){ - var actual = encaseP2(function (){ return Promise.reject(U.error) }, 1, 1); - return U.assertRejected(actual, U.error); - }); - - it('ensures no resolution happens after cancel', function (done){ - var actual = encaseP2(function (x, y){ return Promise.resolve(y + 1) }, 1, 1); - actual._interpret(done, U.failRej, U.failRes)(); - setTimeout(done, 20); - }); - - it('ensures no rejection happens after cancel', function (done){ - var actual = encaseP2(function (x, y){ return Promise.reject(y + 1) }, 1, 1); - actual._interpret(done, U.failRej, U.failRes)(); - setTimeout(done, 20); - }); - - }); - - describe('#toString()', function (){ - - it('returns the code to create the Future', function (){ - var f = function (a, b){ return Promise.resolve(b) }; - var m = encaseP2(f, null, null); - expect(m.toString()).to.equal('encaseP2(' + f.toString() + ', null, null)'); - }); - - }); - -}); diff --git a/test/unit/5.encase-p3.mjs b/test/unit/5.encase-p3.mjs deleted file mode 100644 index dfde4430..00000000 --- a/test/unit/5.encase-p3.mjs +++ /dev/null @@ -1,67 +0,0 @@ -/* eslint prefer-promise-reject-errors: 0 */ - -import chai from 'chai'; -import {encaseP3} from '../../index.mjs'; -import * as U from '../util/util'; -import {testFunction, functionArg, anyArg} from '../util/props'; - -var expect = chai.expect; - -describe('encaseP3()', function (){ - - testFunction('encaseP3', encaseP3, [functionArg, anyArg, anyArg, anyArg], U.assertValidFuture); - - describe('#_interpret()', function (){ - - it('crashes when the Promise generator throws', function (){ - var m = encaseP3(function (){ throw U.error }, 1, 1, 1); - return U.assertCrashed(m, U.error); - }); - - it('crashes when the Promise generator does not return a Promise', function (){ - var m = encaseP3(U.noop, 1, 1, 1); - return U.assertCrashed(m, new TypeError( - 'encaseP3() expects the function it\'s given to return a Promise/Thenable\n' + - ' Actual: undefined\n' + - ' From calling: function (){}\n' + - ' With 1: 1\n' + - ' With 2: 1\n' + - ' With 3: 1' - )); - }); - - it('resolves with the resolution value of the returned Promise', function (){ - var actual = encaseP3(function (x, y, z){ return Promise.resolve(z + 1) }, 1, 1, 1); - return U.assertResolved(actual, 2); - }); - - it('rejects with rejection reason of the returned Promise', function (){ - var actual = encaseP3(function (){ return Promise.reject(U.error) }, 1, 1, 1); - return U.assertRejected(actual, U.error); - }); - - it('ensures no resolution happens after cancel', function (done){ - var actual = encaseP3(function (x, y, z){ return Promise.resolve(z + 1) }, 1, 1, 1); - actual._interpret(done, U.failRej, U.failRes)(); - setTimeout(done, 20); - }); - - it('ensures no rejection happens after cancel', function (done){ - var actual = encaseP3(function (x, y, z){ return Promise.reject(z + 1) }, 1, 1, 1); - actual._interpret(done, U.failRej, U.failRes)(); - setTimeout(done, 20); - }); - - }); - - describe('#toString()', function (){ - - it('returns the code to create the Future', function (){ - var f = function (a, b, c){ return Promise.resolve(c) }; - var m = encaseP3(f, null, null, null); - expect(m.toString()).to.equal('encaseP3(' + f.toString() + ', null, null, null)'); - }); - - }); - -}); diff --git a/test/unit/5.encase2.mjs b/test/unit/5.encase2.mjs deleted file mode 100644 index 3ff55e7b..00000000 --- a/test/unit/5.encase2.mjs +++ /dev/null @@ -1,41 +0,0 @@ -import chai from 'chai'; -import {encase2} from '../../index.mjs'; -import * as U from '../util/util'; -import {testFunction, functionArg, anyArg} from '../util/props'; - -var expect = chai.expect; - -describe('encase2()', function (){ - - testFunction('encase2', encase2, [functionArg, anyArg, anyArg], U.assertValidFuture); - - describe('#_interpret()', function (){ - - it('resolves with the return value of the function', function (){ - var actual = encase2(function (a, x){ return x + 1 }, 1, 1); - return U.assertResolved(actual, 2); - }); - - it('rejects with the exception thrown by the function', function (){ - var actual = encase2(function (a, b){ throw b, U.error }, 1, 1); - return U.assertRejected(actual, U.error); - }); - - it('does not swallow errors from subsequent maps and such', function (){ - var m = encase2(function (x){ return x }, 1, 1).map(function (){ throw U.error }); - return U.assertCrashed(m, U.error); - }); - - }); - - describe('#toString()', function (){ - - it('returns the code to create the Future', function (){ - var f = function (a, b){ return void b }; - var m = encase2(f, null, null); - expect(m.toString()).to.equal('encase2(' + f.toString() + ', null, null)'); - }); - - }); - -}); diff --git a/test/unit/5.encase3.mjs b/test/unit/5.encase3.mjs deleted file mode 100644 index 478cdb19..00000000 --- a/test/unit/5.encase3.mjs +++ /dev/null @@ -1,41 +0,0 @@ -import chai from 'chai'; -import {encase3} from '../../index.mjs'; -import * as U from '../util/util'; -import {testFunction, functionArg, anyArg} from '../util/props'; - -var expect = chai.expect; - -describe('encase3()', function (){ - - testFunction('encase3', encase3, [functionArg, anyArg, anyArg, anyArg], U.assertValidFuture); - - describe('#_interpret()', function (){ - - it('resolves with the return value of the function', function (){ - var actual = encase3(function (a, b, x){ return x + 1 }, 1, 1, 1); - return U.assertResolved(actual, 2); - }); - - it('rejects with the exception thrown by the function', function (){ - var actual = encase3(function (a, b, c){ throw c, U.error }, 1, 1, 1); - return U.assertRejected(actual, U.error); - }); - - it('does not swallow errors from subsequent maps and such', function (){ - var m = encase3(function (x){ return x }, 1, 1, 1).map(function (){ throw U.error }); - return U.assertCrashed(m, U.error); - }); - - }); - - describe('#toString()', function (){ - - it('returns the code to create the Future', function (){ - var f = function (a, b){ return void b }; - var m = encase3(f, null, null, null); - expect(m.toString()).to.equal('encase3(' + f.toString() + ', null, null, null)'); - }); - - }); - -}); diff --git a/test/unit/5.fold.mjs b/test/unit/5.fold.mjs deleted file mode 100644 index 13b188ae..00000000 --- a/test/unit/5.fold.mjs +++ /dev/null @@ -1,21 +0,0 @@ -import {fold, of, reject} from '../../index.mjs'; -import * as U from '../util/util'; -import {testFunction, functionArg, futureArg} from '../util/props'; - -describe('fold()', function (){ - - testFunction('fold', fold, [functionArg, functionArg, futureArg], U.assertValidFuture); - - describe('#_interpret()', function (){ - - it('resolves with the transformed rejection value', function (){ - return U.assertResolved(fold(U.add(1), U.sub(1), reject(1)), 2); - }); - - it('resolves with the transformed resolution value', function (){ - return U.assertResolved(fold(U.sub(1), U.add(1), of(1)), 2); - }); - - }); - -}); diff --git a/test/unit/5.lastly.mjs b/test/unit/5.lastly.mjs deleted file mode 100644 index 111b3437..00000000 --- a/test/unit/5.lastly.mjs +++ /dev/null @@ -1,53 +0,0 @@ -import {lastly, of, reject, finally as fin} from '../../index.mjs'; -import * as U from '../util/util'; -import * as F from '../util/futures'; -import {testFunction, futureArg} from '../util/props'; - -describe('lastly()', function (){ - - testFunction('lastly', lastly, [futureArg, futureArg], U.assertValidFuture); - - it('is an alias of "finally"', function (){ - U.eq(lastly, fin); - }); - - describe('#_interpret()', function (){ - - it('runs the second Future when the first resolves', function (done){ - lastly(of(null).map(done), of(1))._interpret(done, U.noop, U.noop); - }); - - it('runs the second Future when the first rejects', function (done){ - lastly(of(null).map(done), reject(1))._interpret(done, U.noop, U.noop); - }); - - it('resolves with the resolution value of the first', function (){ - var actual = lastly(of(2), of(1)); - return U.assertResolved(actual, 1); - }); - - it('rejects with the rejection reason of the first if the second resolves', function (){ - var actual = lastly(of(2), reject(1)); - return U.assertRejected(actual, 1); - }); - - it('always rejects with the rejection reason of the second', function (){ - var actualResolved = lastly(reject(2), of(1)); - var actualRejected = lastly(reject(2), reject(1)); - return Promise.all([ - U.assertRejected(actualResolved, 2), - U.assertRejected(actualRejected, 2) - ]); - }); - - it('does nothing after being cancelled', function (done){ - lastly(F.resolved, F.resolvedSlow)._interpret(done, U.failRej, U.failRes)(); - lastly(F.resolvedSlow, F.resolved)._interpret(done, U.failRej, U.failRes)(); - lastly(F.rejected, F.rejectedSlow)._interpret(done, U.failRej, U.failRes)(); - lastly(F.rejectedSlow, F.rejected)._interpret(done, U.failRej, U.failRes)(); - setTimeout(done, 25); - }); - - }); - -}); diff --git a/test/unit/5.map-rej.mjs b/test/unit/5.map-rej.mjs deleted file mode 100644 index b752538a..00000000 --- a/test/unit/5.map-rej.mjs +++ /dev/null @@ -1,34 +0,0 @@ -import {mapRej} from '../../index.mjs'; -import * as U from '../util/util'; -import * as F from '../util/futures'; -import {testFunction, functionArg, futureArg} from '../util/props'; - -describe('mapRej()', function (){ - - testFunction('mapRej', mapRej, [functionArg, futureArg], U.assertValidFuture); - - describe('#_interpret()', function (){ - - it('applies the given function to its rejection reason', function (){ - var actual = mapRej(U.bang, F.rejected); - return U.assertRejected(actual, 'rejected!'); - }); - - it('does not map resolved state', function (){ - var actual = mapRej(function (){ return 'mapped' }, F.resolved); - return U.assertResolved(actual, 'resolved'); - }); - - it('does not resolve after being cancelled', function (done){ - mapRej(U.failRej, F.resolvedSlow)._interpret(done, U.failRej, U.failRes)(); - setTimeout(done, 25); - }); - - it('does not reject after being cancelled', function (done){ - mapRej(U.failRej, F.rejectedSlow)._interpret(done, U.failRej, U.failRes)(); - setTimeout(done, 25); - }); - - }); - -}); diff --git a/test/unit/5.map.mjs b/test/unit/5.map.mjs deleted file mode 100644 index ced1ce82..00000000 --- a/test/unit/5.map.mjs +++ /dev/null @@ -1,34 +0,0 @@ -import {map} from '../../index.mjs'; -import * as U from '../util/util'; -import * as F from '../util/futures'; -import {testFunction, functionArg, functorArg} from '../util/props'; - -describe('map()', function (){ - - testFunction('map', map, [functionArg, functorArg], U.assertValidFuture); - - describe('#_interpret()', function (){ - - it('applies the given function to its inner', function (){ - var actual = map(U.bang, F.resolved); - return U.assertResolved(actual, 'resolved!'); - }); - - it('does not map rejected state', function (){ - var actual = map(function (){ return 'mapped' }, F.rejected); - return U.assertRejected(actual, 'rejected'); - }); - - it('does not resolve after being cancelled', function (done){ - map(U.failRes, F.resolvedSlow)._interpret(done, U.failRej, U.failRes)(); - setTimeout(done, 25); - }); - - it('does not reject after being cancelled', function (done){ - map(U.failRes, F.rejectedSlow)._interpret(done, U.failRej, U.failRes)(); - setTimeout(done, 25); - }); - - }); - -}); diff --git a/test/unit/6.par.mjs b/test/unit/5.par.mjs similarity index 55% rename from test/unit/6.par.mjs rename to test/unit/5.par.mjs index bc9cd5d6..9dc241b7 100644 --- a/test/unit/6.par.mjs +++ b/test/unit/5.par.mjs @@ -1,8 +1,8 @@ import chai from 'chai'; -import {Future, Par, seq, of, reject, never, ap, map, alt} from '../../index.mjs'; -import * as U from '../util/util'; -import * as F from '../util/futures'; import Z from 'sanctuary-type-classes'; +import {Future, Par, seq, resolve, reject, never, ap, map, alt, and} from '../../index.mjs'; +import {add, assertCrashed, assertRejected, assertResolved, bang, error, noop, throws} from '../util/util'; +import {rejected, resolved, resolvedSlow} from '../util/futures'; var expect = chai.expect; @@ -15,7 +15,7 @@ describe('Par()', function (){ it('throws when not given a Future', function (){ var f = function (){ return Par(1) }; - U.throws(f, new TypeError( + throws(f, new TypeError( 'ConcurrentFuture expects its first argument to be of type "Future"\n' + ' Actual: 1' )); @@ -23,21 +23,17 @@ describe('Par()', function (){ describe('.of()', function (){ - var of = Z.of; - it('resolves with the value', function (){ - var m = of(Par, 1); - return U.assertResolved(seq(m), 1); + var m = Z.of(Par, 1); + return assertResolved(seq(m), 1); }); }); describe('.zero()', function (){ - var zero = Z.zero; - it('creates a never-ending ConcurrentFuture', function (){ - var m = zero(Par); + var m = Z.zero(Par); expect(seq(m)).to.equal(never); }); @@ -45,54 +41,54 @@ describe('Par()', function (){ describe('#ap()', function (){ - var mf = of(U.bang); + var mf = resolve(bang); it('throws TypeError when the Future does not resolve to a Function', function (){ - var m = seq(ap(Par(of(1)), Par(F.resolved))); - return U.assertCrashed(m, new TypeError( - 'Future#_parallelAp() expects its first argument to be a Future of a Function\n' + - ' Actual: Future.of(1)' + var m = seq(ap(Par(resolved))(Par(resolve(1)))); + return assertCrashed(m, new TypeError( + 'parallelAp expects the second Future to resolve to a Function\n' + + ' Actual: 1' )); }); it('calls the function contained in the given Future to its contained value', function (){ - var actual = ap(Par(mf), Par(F.resolved)); - return U.assertResolved(seq(actual), 'resolved!'); + var actual = ap(Par(resolved))(Par(mf)); + return assertResolved(seq(actual), 'resolved!'); }); it('rejects if one of the two reject', function (){ - var left = ap(Par(mf), Par(F.rejected)); - var right = ap(Par(F.rejected), Par(F.resolved)); + var left = ap(Par(rejected))(Par(mf)); + var right = ap(Par(resolved))(Par(rejected)); return Promise.all([ - U.assertRejected(seq(left), 'rejected'), - U.assertRejected(seq(right), 'rejected') + assertRejected(seq(left), 'rejected'), + assertRejected(seq(right), 'rejected') ]); }); it('does not matter if either resolves late', function (){ - var left = ap(Par(mf), Par(F.resolvedSlow)); - var right = ap(Par(F.resolvedSlow.and(mf)), Par(F.resolved)); + var left = ap(Par(resolvedSlow))(Par(mf)); + var right = ap(Par(resolved))(Par(and(mf)(resolvedSlow))); return Promise.all([ - U.assertResolved(seq(left), 'resolvedSlow!'), - U.assertResolved(seq(right), 'resolved!') + assertResolved(seq(left), 'resolvedSlow!'), + assertResolved(seq(right), 'resolved!') ]); }); it('cannot reject twice', function (){ - var actual = ap(Par(F.rejected), Par(F.rejected)); - return U.assertRejected(seq(actual), 'rejected'); + var actual = ap(Par(rejected))(Par(rejected)); + return assertRejected(seq(actual), 'rejected'); }); it('creates a cancel function which cancels both Futures', function (done){ var cancelled = false; var m = Par(Future(function (){ return function (){ return (cancelled ? done() : (cancelled = true)) } })); - var cancel = seq(ap(m, m))._interpret(done, U.noop, U.noop); + var cancel = seq(ap(m)(m))._interpret(done, noop, noop); cancel(); }); it('shows a reasonable representation when cast to string', function (){ - var m = ap(Par(of(1)), Par(reject(0))); - var s = 'ConcurrentFuture(Future.of(1)._parallelAp(reject(0)))'; + var m = ap(Par(reject(0)))(Par(resolve(1))); + var s = 'ConcurrentFuture(parallelAp (reject (0)) (resolve (1)))'; expect(m.toString()).to.equal(s); }); @@ -101,18 +97,18 @@ describe('Par()', function (){ describe('#map()', function (){ it('applies the given function to its inner', function (){ - var actual = map(U.add(1), Par(of(1))); - return U.assertResolved(seq(actual), 2); + var actual = map(add(1))(Par(resolve(1))); + return assertResolved(seq(actual), 2); }); it('does not map rejected state', function (){ - var actual = map(function (){ return 'mapped' }, Par(F.rejected)); - return U.assertRejected(seq(actual), 'rejected'); + var actual = map(function (){ return 'mapped' })(Par(rejected)); + return assertRejected(seq(actual), 'rejected'); }); it('shows a reasonable representation when cast to string', function (){ - var m = map(U.noop, Par(F.resolved)); - var expected = 'ConcurrentFuture(Future.of("resolved").map(' + (U.noop.toString()) + '))'; + var m = map(noop)(Par(resolved)); + var expected = 'ConcurrentFuture(map (' + noop.toString() + ') (resolve ("resolved")))'; expect(m.toString()).to.equal(expected); }); @@ -122,19 +118,19 @@ describe('Par()', function (){ it('rejects when the first one rejects', function (){ var m1 = Par(Future(function (rej, res){ return void setTimeout(res, 50, 1) })); - var m2 = Par(Future(function (rej){ return void setTimeout(rej, 5, U.error) })); - return U.assertRejected(seq(alt(m1, m2)), U.error); + var m2 = Par(Future(function (rej){ return void setTimeout(rej, 5, error) })); + return assertRejected(seq(alt(m1)(m2)), error); }); it('resolves when the first one resolves', function (){ var m1 = Par(Future(function (rej, res){ return void setTimeout(res, 5, 1) })); - var m2 = Par(Future(function (rej){ return void setTimeout(rej, 50, U.error) })); - return U.assertResolved(seq(alt(m1, m2)), 1); + var m2 = Par(Future(function (rej){ return void setTimeout(rej, 50, error) })); + return assertResolved(seq(alt(m1)(m2)), 1); }); it('shows a reasonable representation when cast to string', function (){ - var m = alt(Par(of(2)), Par(of(1))); - var s = 'ConcurrentFuture(Future.of(1).race(Future.of(2)))'; + var m = alt(Par(resolve(1)))(Par(resolve(2))); + var s = 'ConcurrentFuture(race (resolve (1)) (resolve (2)))'; expect(m.toString()).to.equal(s); }); diff --git a/test/unit/5.parallel-ap.mjs b/test/unit/5.parallel-ap.mjs deleted file mode 100644 index de858770..00000000 --- a/test/unit/5.parallel-ap.mjs +++ /dev/null @@ -1,133 +0,0 @@ -import Future from '../../index.cjs'; -import {of} from '../../index.mjs'; -import * as U from '../util/util'; -import * as F from '../util/futures'; - -var parallelAp = function (a, b){ return b._parallelAp(a) }; - -describe('parallelAp()', function (){ - - it('is not currently exposed as a static function', function (){ - U.eq(Future.parallelAp, undefined); - }); - - describe('#_interpret()', function (){ - - var mapf = function (m){ return m.map(function (x){ return function (y){ return [x, y] } }) }; - - describe('(Crashed, Resolved)', function (){ - - it('crashes if left settles first', function (){ - return U.assertCrashed(parallelAp(F.crashed, mapf(F.resolvedSlow)), U.error); - }); - - it('crashes if left settles last', function (){ - return U.assertCrashed(parallelAp(F.crashedSlow, mapf(F.resolved)), U.error); - }); - - }); - - describe('(Crashed, Rejected)', function (){ - - it('crashes if left settles first', function (){ - return U.assertCrashed(parallelAp(F.crashed, mapf(F.rejectedSlow)), U.error); - }); - - it('rejects if left settles last', function (){ - return U.assertRejected(parallelAp(F.crashedSlow, mapf(F.rejected)), 'rejected'); - }); - - }); - - describe('(Resolved, Crashed)', function (){ - - it('crashes if left settles first', function (){ - return U.assertCrashed(parallelAp(F.resolved, mapf(F.crashedSlow)), U.error); - }); - - it('crashes if left settles last', function (){ - return U.assertCrashed(parallelAp(F.resolvedSlow, mapf(F.crashed)), new Error( - 'Intentional error for unit testing' - )); - }); - - }); - - describe('(Rejected, Crashed)', function (){ - - it('rejects if left settles first', function (){ - return U.assertRejected(parallelAp(F.rejected, mapf(F.crashedSlow)), 'rejected'); - }); - - it('crashes if left settles last', function (){ - return U.assertCrashed(parallelAp(F.rejectedSlow, mapf(F.crashed)), new Error( - 'Intentional error for unit testing' - )); - }); - - }); - - describe('(Resolved, Resolved)', function (){ - - it('resolves with parallelAp if left settles first', function (){ - return U.assertResolved(parallelAp(F.resolved, mapf(F.resolvedSlow)), ['resolvedSlow', 'resolved']); - }); - - it('resolves with parallelAp if left settles last', function (){ - return U.assertResolved(parallelAp(F.resolvedSlow, mapf(F.resolved)), ['resolved', 'resolvedSlow']); - }); - - }); - - describe('(Rejected, Rejected)', function (){ - - it('rejects with right if right rejects first', function (){ - return U.assertRejected(parallelAp(F.rejectedSlow, mapf(F.rejected)), 'rejected'); - }); - - it('rejects with left if right rejects last', function (){ - return U.assertRejected(parallelAp(F.rejected, mapf(F.rejectedSlow)), 'rejected'); - }); - - }); - - describe('(Rejected, Resolved)', function (){ - - it('rejects with left if right settles first', function (){ - return U.assertRejected(parallelAp(F.rejectedSlow, mapf(F.resolved)), 'rejectedSlow'); - }); - - it('rejects with left if right settles last', function (){ - return U.assertRejected(parallelAp(F.rejected, mapf(F.resolvedSlow)), 'rejected'); - }); - - }); - - describe('(Resolved, Rejected)', function (){ - - it('rejects with right if left settles first', function (){ - return U.assertRejected(parallelAp(F.resolved, mapf(F.rejectedSlow)), 'rejectedSlow'); - }); - - it('rejects with right if left settles last', function (){ - return U.assertRejected(parallelAp(F.resolvedSlow, mapf(F.rejected)), 'rejected'); - }); - - }); - - it('crashes when the other does not resolve to a Function', function (){ - var m = parallelAp(of(1), of(null)); - return U.assertCrashed(m, new TypeError( - 'Future#_parallelAp() expects its first argument to be a Future of a Function\n' + - ' Actual: Future.of(null)' - )); - }); - - it('calls the function contained in the given Future to its contained value', function (){ - var actual = parallelAp(of(1), of(U.add(1))); - return U.assertResolved(actual, 2); - }); - - }); - -}); diff --git a/test/unit/5.race.mjs b/test/unit/5.race.mjs deleted file mode 100644 index ee29a1ce..00000000 --- a/test/unit/5.race.mjs +++ /dev/null @@ -1,149 +0,0 @@ -import {Future, race} from '../../index.mjs'; -import * as U from '../util/util'; -import * as F from '../util/futures'; -import {testFunction, futureArg} from '../util/props'; - -describe('race()', function (){ - - testFunction('race', race, [futureArg, futureArg], U.assertValidFuture); - - describe('#_interpret()', function (){ - - describe('(Crashed, Resolved)', function (){ - - it('crashes if left settles first', function (){ - return U.assertCrashed(race(F.crashed, F.resolvedSlow), U.error); - }); - - it('resolves if left settles last', function (){ - return U.assertResolved(race(F.crashedSlow, F.resolved), 'resolved'); - }); - - }); - - describe('(Crashed, Rejected)', function (){ - - it('crashes if left settles first', function (){ - return U.assertCrashed(race(F.crashed, F.rejectedSlow), U.error); - }); - - it('rejects if left settles last', function (){ - return U.assertRejected(race(F.crashedSlow, F.rejected), 'rejected'); - }); - - }); - - describe('(Resolved, Crashed)', function (){ - - it('resolves if left settles first', function (){ - return U.assertResolved(race(F.resolved, F.crashedSlow), 'resolved'); - }); - - it('crashes if left settles last', function (){ - return U.assertCrashed(race(F.resolvedSlow, F.crashed), U.error); - }); - - }); - - describe('(Rejected, Crashed)', function (){ - - it('rejects if left settles first', function (){ - return U.assertRejected(race(F.rejected, F.crashedSlow), 'rejected'); - }); - - it('crashes if left settles last', function (){ - return U.assertCrashed(race(F.rejectedSlow, F.crashed), U.error); - }); - - }); - - describe('(Resolved, Resolved)', function (){ - - it('resolves with left if left settles first', function (){ - return U.assertResolved(race(F.resolved, F.resolvedSlow), 'resolved'); - }); - - it('resolves with right if left settles last', function (){ - return U.assertResolved(race(F.resolvedSlow, F.resolved), 'resolved'); - }); - - }); - - describe('(Rejected, Rejected)', function (){ - - it('rejects with right if right rejects first', function (){ - return U.assertRejected(race(F.rejectedSlow, F.rejected), 'rejected'); - }); - - it('rejects with left if right rejects last', function (){ - return U.assertRejected(race(F.rejected, F.rejectedSlow), 'rejected'); - }); - - }); - - describe('(Rejected, Resolved)', function (){ - - it('resolves with right if right settles first', function (){ - return U.assertResolved(race(F.rejectedSlow, F.resolved), 'resolved'); - }); - - it('rejects with left if right settles last', function (){ - return U.assertRejected(race(F.rejected, F.resolvedSlow), 'rejected'); - }); - - }); - - describe('(Resolved, Rejected)', function (){ - - it('resolves with left if left settles first', function (){ - return U.assertResolved(race(F.resolved, F.rejectedSlow), 'resolved'); - }); - - it('rejects with right if left settles last', function (){ - return U.assertRejected(race(F.resolvedSlow, F.rejected), 'rejected'); - }); - - }); - - it('rejects when the first one rejects', function (){ - var m1 = Future(function (rej, res){ return void setTimeout(res, 50, 1) }); - var m2 = Future(function (rej){ return void setTimeout(rej, 5, U.error) }); - return U.assertRejected(race(m1, m2), U.error); - }); - - it('resolves when the first one resolves', function (){ - var m1 = Future(function (rej, res){ return void setTimeout(res, 5, 1) }); - var m2 = Future(function (rej){ return void setTimeout(rej, 50, U.error) }); - return U.assertResolved(race(m1, m2), 1); - }); - - it('cancels the right if the left resolves', function (done){ - var m = race(F.resolvedSlow, Future(function (){ return function (){ return done() } })); - m._interpret(done, U.noop, U.noop); - }); - - it('cancels the left if the right resolves', function (done){ - var m = race(Future(function (){ return function (){ return done() } }), F.resolvedSlow); - m._interpret(done, U.noop, U.noop); - }); - - it('cancels the right if the left rejects', function (done){ - var m = race(F.rejectedSlow, Future(function (){ return function (){ return done() } })); - m._interpret(done, U.noop, U.noop); - }); - - it('cancels the left if the right rejects', function (done){ - var m = race(Future(function (){ return function (){ return done() } }), F.rejectedSlow); - m._interpret(done, U.noop, U.noop); - }); - - it('creates a cancel function which cancels both Futures', function (done){ - var cancelled = false; - var m = Future(function (){ return function (){ return (cancelled ? done() : (cancelled = true)) } }); - var cancel = race(m, m)._interpret(done, U.noop, U.noop); - cancel(); - }); - - }); - -}); diff --git a/test/unit/6.seq.mjs b/test/unit/5.seq.mjs similarity index 100% rename from test/unit/6.seq.mjs rename to test/unit/5.seq.mjs diff --git a/test/unit/5.swap.mjs b/test/unit/5.swap.mjs deleted file mode 100644 index fa14a5db..00000000 --- a/test/unit/5.swap.mjs +++ /dev/null @@ -1,23 +0,0 @@ -import {swap, of, reject} from '../../index.mjs'; -import * as U from '../util/util'; -import {testFunction, futureArg} from '../util/props'; - -describe('swap()', function (){ - - testFunction('swap', swap, [futureArg], U.assertValidFuture); - - describe('#_interpret()', function (){ - - it('rejects with the resolution value', function (){ - var actual = swap(of(1)); - return U.assertRejected(actual, 1); - }); - - it('resolves with the rejection reason', function (){ - var actual = swap(reject(1)); - return U.assertResolved(actual, 1); - }); - - }); - -}); diff --git a/test/util/futures.mjs b/test/util/futures.mjs index 0ea097f0..cb1cbeb0 100644 --- a/test/util/futures.mjs +++ b/test/util/futures.mjs @@ -1,14 +1,14 @@ -import {of, reject, after, rejectAfter} from '../../index.mjs'; -import {Future, Crashed} from '../../src/future'; +import {Future, resolve, reject, after, rejectAfter, and} from '../../index.mjs'; +import {crash} from '../../src/crash'; import {error} from '../util/util'; export var mock = Object.create(Future.prototype); mock._interpret = function (){ throw new Error('Override _interpret on mock Future') }; mock.toString = function (){ return '(util.mock)' }; -export var resolved = of('resolved'); +export var resolved = resolve('resolved'); export var rejected = reject('rejected'); -export var resolvedSlow = after(20, 'resolvedSlow'); -export var rejectedSlow = rejectAfter(20, 'rejectedSlow'); -export var crashed = new Crashed(error); -export var crashedSlow = after(20, null).and(crashed); +export var resolvedSlow = after(20)('resolvedSlow'); +export var rejectedSlow = rejectAfter(20)('rejectedSlow'); +export var crashed = crash(error); +export var crashedSlow = and(crashed)(after(20)(null)); diff --git a/test/util/props.mjs b/test/util/props.mjs index a2290153..b4d35ba1 100644 --- a/test/util/props.mjs +++ b/test/util/props.mjs @@ -22,8 +22,6 @@ export var elements = jsc.elements; export var suchthat = jsc.suchthat; export var nil = elements([null, undefined]); -var R_VOWEL = /^[aeiouyAEIOUY]/; - export function f (x){ return {f: x}; } @@ -77,10 +75,6 @@ export function _of (rarb){ return FutureArb(string, rarb); } -export function an (thing){ - return R_VOWEL.test(thing) ? ('an ' + thing) : ('a ' + thing); -} - export var { any, anyFuture, @@ -112,73 +106,73 @@ export var { export var anyParallel = anyFuture.smap(Par, seq, show); export var altArg = { - name: 'Alt', + name: 'have Alt implemented', valid: anyFuture, invalid: anyNonFuture, }; export var applyArg = { - name: 'Apply', + name: 'have Apply implemented', valid: anyFuture, invalid: anyNonFuture, }; export var bifunctorArg = { - name: 'Bifunctor', + name: 'have Bifunctor implemented', valid: anyFuture, invalid: anyNonFuture, }; export var chainArg = { - name: 'Chain', + name: 'have Chain implemented', valid: anyFuture, invalid: anyNonFuture, }; export var functorArg = { - name: 'Functor', + name: 'have Functor implemented', valid: anyFuture, invalid: anyNonFuture, }; export var functionArg = { - name: 'Function', + name: 'be a Function', valid: anyFunction, invalid: oneof(number, string, bool, falsy, constant(error)), }; export var futureArg = { - name: 'valid Future', + name: 'be a valid Future', valid: anyFuture, invalid: anyNonFuture, }; export var resolvedFutureArg = { - name: 'valid Future', + name: 'be a valid Future', valid: anyResolvedFuture, invalid: anyNonFuture, }; export var positiveIntegerArg = { - name: 'positive Integer', + name: 'be a positive Integer', valid: suchthat(nat, function (x){ return x > 0 }), invalid: oneof(bool, constant(0.5)), }; export var futureArrayArg = { - name: 'Array of valid Futures', + name: 'be an Array of valid Futures', valid: array(anyFuture), invalid: oneof(nearray(anyNonFuture), any), }; export var parallelArg = { - name: 'ConcurrentFuture', + name: 'be a ConcurrentFuture', valid: anyParallel, invalid: any, }; export var anyArg = { - name: 'Any', + name: 'be anything', valid: any, invalid: null, }; @@ -196,12 +190,11 @@ export function testFunction (name, func, args, assert){ it('is a curried ' + args.length + '-ary function', function (){ eq(typeof func, 'function'); - eq(func.length, args.length); + eq(func.length, 1); validArgs.slice(0, -1).forEach(function (_, idx){ - var partial1 = func.apply(null, validArgs.slice(0, idx + 1)); - var partial2 = capply(func, validArgs.slice(0, idx + 1)); - eq(typeof partial1, 'function'); - eq(typeof partial2, 'function'); + var partial = capply(func, validArgs.slice(0, idx + 1)); + eq(typeof partial, 'function'); + eq(partial.length, 1); }); }); @@ -213,9 +206,9 @@ export function testFunction (name, func, args, assert){ if(arg !== anyArg){ property('throws when the ' + ordinal[idx] + ' argument is invalid', arg.invalid, function (value){ throws(function (){ - func.apply(null, validPriorArgs.concat([value]).concat(validFollowingArgs)); + capply(func, validPriorArgs.concat([value]).concat(validFollowingArgs)); }, new TypeError( - name + '() expects its ' + ordinal[idx] + ' argument to be ' + an(arg.name) + '.\n' + + name + '() expects its ' + ordinal[idx] + ' argument to ' + arg.name + '.\n' + ' Actual: ' + show(value) + ' :: ' + type.parse(type(value)).name )); return true; @@ -224,40 +217,6 @@ export function testFunction (name, func, args, assert){ }); property.apply(null, ['returns valid output when given valid input'].concat(validArbs).concat([function (){ - return assert(func.apply(null, arguments)); + return assert(capply(func, Array.from(arguments))); }])); } - -export function testMethod (instance, method, args){ - var validArgs = args.map(generateValid); - - it('exists', function (){ - eq(typeof instance[method], 'function'); - eq(instance[method].length, args.length); - }); - - property('throws when invoked out of context', anyNonFuture, function (value){ - throws(function (){ instance[method].apply(value, validArgs) }, new TypeError( - 'Future#' + method + '() was invoked outside the context of a Future. ' + - 'You might want to use a dispatcher instead\n' + - ' Called on: ' + show(value) - )); - return true; - }); - - args.forEach(function (arg, idx){ - var priorArgs = args.slice(0, idx); - var followingArgs = args.slice(idx + 1); - var validPriorArgs = priorArgs.map(generateValid); - var validFollowingArgs = followingArgs.map(generateValid); - property('throws when the ' + ordinal[idx] + ' argument is invalid', arg.invalid, function (value){ - throws(function (){ - instance[method].apply(instance, validPriorArgs.concat([value]).concat(validFollowingArgs)); - }, new TypeError( - 'Future#' + method + '() expects its ' + ordinal[idx] + ' argument to be ' + an(arg.name) + '.\n' + - ' Actual: ' + show(value) + ' :: ' + type.parse(type(value)).name - )); - return true; - }); - }); -} diff --git a/test/util/util.mjs b/test/util/util.mjs index fb5ff474..ed251330 100644 --- a/test/util/util.mjs +++ b/test/util/util.mjs @@ -1,6 +1,7 @@ import show from 'sanctuary-show'; import type from 'sanctuary-type-identifiers'; import {Future, isFuture, reject, resolve} from '../../index.mjs'; +import {crash} from '../../src/crash'; import {strictEqual, deepStrictEqual} from 'assert'; export * from '../../src/internal/predicates'; @@ -15,6 +16,7 @@ export var K = function (x){ return function (){ return x } }; export var T = function (x){ return function (f){ return f(x) } }; export var error = new Error('Intentional error for unit testing'); export var throwit = function (it){ throw it }; +export var throwing = function (){ throw error }; export var eq = function eq (actual, expected){ strictEqual(arguments.length, eq.length); @@ -30,27 +32,27 @@ export var throws = function throws (f, expected){ try{ f(); }catch(actual){ - eq(actual, expected); + eq(typeof actual, typeof expected); + eq(actual.constructor, expected.constructor); + eq(actual.name, expected.name); + eq(actual.message, expected.message); return; } throw new Error('Expected the function to throw'); }; -export var raises = function raises (done, fn, expected){ - if(typeof process.rawListeners !== 'function'){ - done(); - return; - } - - var listeners = process.rawListeners('uncaughtException'); - process.removeAllListeners('uncaughtException'); - process.once('uncaughtException', function (actual){ - listeners.forEach(function (f){ process.on('uncaughtException', f) }); - eq(actual.message, expected.message); - done(); +export var itRaises = function itRaises (when, f, e){ + var test = (typeof process.rawListeners === 'function') ? it : it.skip; + test('raises ' + when, function (done){ + var listeners = process.rawListeners('uncaughtException'); + process.removeAllListeners('uncaughtException'); + process.once('uncaughtException', function (actual){ + listeners.forEach(function (f){ process.on('uncaughtException', f) }); + eq(actual.message, e.message); + done(); + }); + f(); }); - - fn(); }; export var isDeepStrictEqual = function isDeepStrictEqual (actual, expected){ @@ -198,9 +200,7 @@ var assertEqualByErrorMessage = makeAssertEqual(function (a, b){ }); export var assertCrashed = function (m, x){ - return assertEqualByErrorMessage(m, Future(function (){ - throw x; - })); + return assertEqualByErrorMessage(m, crash(x)); }; export var assertRejected = function (m, x){