Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Catch exceptions #224

Merged
merged 1 commit into from
Feb 26, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ node_js:
- "7"
- "8"
- "9"
after_success: npm run test:coverage
after_success: npm run test:coverage && npm run coverage:upload
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1031,8 +1031,8 @@ Future.prototype.value :: Future a b ~> (b -> x) -> Cancel

Extracts the value from a resolved Future by forking it. Only use this function
if you are sure the Future is going to be resolved, for example; after using
`.fold()`. If the Future rejects and `value` was used, an (likely uncatchable)
`Error` will be thrown.
`.fold()`. If the Future rejects and `value` was used, an uncatchable `Error`
will be thrown.

```js
Future.reject(new Error('It broke'))
Expand Down
44 changes: 44 additions & 0 deletions bench/fluture.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,54 @@ module.exports = require('sanctuary-benchmark')(Old, New, config, {
{}, ({of}) => of(1)
],

'def.construct.reject': [
{}, ({reject}) => reject(1)
],

'def.construct.after': [
{}, ({after}) => after(1, 1)
],

'def.construct.attempt': [
{}, ({attempt}) => attempt(noop)
],

'def.construct.cache': [
{}, ({of, cache}) => cache(of(1))
],

'def.construct.chainRec': [
{}, ({chainRec}) => chainRec(noop, 1)
],

'def.construct.encase': [
{}, ({encase}) => encase(noop, 1)
],

'def.construct.encaseN': [
{}, ({encaseN}) => encaseN(noop, 1)
],

'def.construct.encaseP': [
{}, ({encaseP}) => encaseP(noop, 1)
],

'def.construct.go': [
{}, ({go}) => go(noop)
],

'def.construct.hook': [
{}, ({of, hook}) => hook(of(1), noop, noop)
],

'def.construct.node': [
{}, ({node}) => node(done => done(null, 1))
],

'def.construct.parallel': [
{}, ({of, parallel}) => parallel(1, [of(1)])
],

'def.transform.ap': [
{}, ({of, ap}) => ap(of(plus1), of(1))
],
Expand Down
12 changes: 3 additions & 9 deletions index.mjs.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
import {error, invalidArgument} from './src/internal/throw';

if(typeof Object.create !== 'function') error('Please polyfill Object.create to use Fluture');
if(typeof Object.assign !== 'function') error('Please polyfill Object.assign to use Fluture');
if(typeof Array.isArray !== 'function') error('Please polyfill Array.isArray to use Fluture');
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This chunk was useless, because rollup changes the order of code evaluation in such a way that this code is reached only after some parts have already tried to use these functions. It's also not super important, as it only served to slightly improve the error messages.


import concurrify from 'concurrify';
import type from 'sanctuary-type-identifiers';
import {throwInvalidArgument} from './src/internal/throw';
import {Future, of, reject, never} from './src/core';
import {FL} from './src/internal/const';
import {chainRec} from './src/chain-rec';
import {ap, map, bimap, chain, race, alt} from './src/dispatchers';
import {parallelAp} from './src/parallel-ap';

Future.of = Future[FL.of] = of;
Future.chainRec = Future[FL.chainRec] = chainRec;
Expand All @@ -20,7 +14,7 @@ Future.map = map;
Future.bimap = bimap;
Future.chain = chain;

var Par = concurrify(Future, never, race, parallelAp);
var Par = concurrify(Future, never, race, function parallelAp(a, b){ return b._parallelAp(a) });
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed parallelAp from a static constructor to a standard Action. This saved me writing some extra code.

Par.of = Par[FL.of];
Par.zero = Par[FL.zero];
Par.map = map;
Expand All @@ -32,7 +26,7 @@ function isParallel(x){
}

function seq(par){
if(!isParallel(par)) invalidArgument('Future.seq', 0, 'to be a Par', par);
if(!isParallel(par)) throwInvalidArgument('Future.seq', 0, 'to be a Par', par);
return par.sequential;
}

Expand Down
15 changes: 9 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,18 @@
],
"repository": "https://github.com/fluture-js/Fluture.git",
"scripts": {
"bench": "sanctuary-benchmark",
"bench": "npm run build && sanctuary-benchmark",
"build": "rollup -c",
"clean": "rimraf npm-debug.log coverage index.js index.test.js",
"clean": "rimraf npm-debug.log coverage index.js index.test.js .esm-cache .nyc_output node_modules/.cache",
"lint": "eslint src test index.es.js README.md",
"lint:readme": "remark --no-stdout --frail -u remark-validate-links README.md",
"release": "npm outdated --long; 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:types",
"test:unit": "npm run build && nyc --include src mocha --require @std/esm --ui bdd --reporter list --check-leaks --full-trace test/**.test.js",
"test:coverage": "nyc report --reporter=text-lcov > coverage.lcov && codecov",
"test": "npm run lint && npm run lint:readme && npm run test:unit && npm run test:types && npm run test:build",
"test:unit": "npm run clean && mocha --require @std/esm --ui bdd --reporter list --full-trace --check-leaks --bail test/**.test.js",
"test:build": "npm run clean && npm run build && mocha --require @std/esm --ui bdd --reporter dot --bail test/**.buildtest.js",
"test:coverage": "npm run clean && nyc --include src mocha --require @std/esm --ui bdd --reporter dot test/**.test.js || true",
"coverage:upload": "nyc report --reporter=text-lcov > coverage.lcov && codecov",
"coverage:report": "nyc report --reporter=html",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes facilitate potentially failing tests while testing for coverage -- annoyingly when testing toString, sometimes the coverage instrumentation breaks expected output.

"test:types": "tsc index.d.ts"
},
"author": "Aldwin Vlasblom <aldwin.vlasblom@gmail.com> (https://github.com/Avaq)",
Expand Down Expand Up @@ -73,7 +76,7 @@
"fantasy-states": "^0.2.1",
"jsverify": "^0.8.3",
"mocha": "^4.0.0",
"nyc": "^11.2.1",
"nyc": "^11.4.1",
"ramda": "^0.25.0",
"remark-cli": "^5.0.0",
"remark-validate-links": "^7.0.0",
Expand Down
12 changes: 7 additions & 5 deletions src/after.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {Core, isNever, never} from './core';
import {show, partial1} from './internal/fn';
import {isUnsigned} from './internal/is';
import {invalidArgument} from './internal/throw';
import {throwInvalidArgument} from './internal/throw';

function After$race(other){
return other.isSettled()
Expand All @@ -26,7 +26,7 @@ After.prototype._swap = function After$swap(){
return new RejectAfter(this._time, this._value);
};

After.prototype._fork = function After$_fork(rej, res){
After.prototype._interpret = function After$interpret(rec, rej, res){
var id = setTimeout(res, this._time, this._value);
return function After$cancel(){ clearTimeout(id) };
};
Expand All @@ -52,7 +52,7 @@ RejectAfter.prototype._swap = function RejectAfter$swap(){
return new After(this._time, this._value);
};

RejectAfter.prototype._fork = function RejectAfter$_fork(rej){
RejectAfter.prototype._interpret = function RejectAfter$interpret(rec, rej){
var id = setTimeout(rej, this._time, this._value);
return function RejectAfter$cancel(){ clearTimeout(id) };
};
Expand All @@ -70,7 +70,7 @@ function after$time(time, value){
}

export function after(time, value){
if(!isUnsigned(time)) invalidArgument('Future.after', 0, 'be a positive integer', time);
if(!isUnsigned(time)) throwInvalidArgument('Future.after', 0, 'be a positive integer', time);
if(arguments.length === 1) return partial1(after$time, time);
return after$time(time, value);
}
Expand All @@ -80,7 +80,9 @@ function rejectAfter$time(time, reason){
}

export function rejectAfter(time, reason){
if(!isUnsigned(time)) invalidArgument('Future.rejectAfter', 0, 'be a positive integer', time);
if(!isUnsigned(time)){
throwInvalidArgument('Future.rejectAfter', 0, 'be a positive integer', time);
}
if(arguments.length === 1) return partial1(rejectAfter$time, time);
return rejectAfter$time(time, reason);
}
6 changes: 3 additions & 3 deletions src/attempt.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import {Core} from './core';
import {noop, showf} from './internal/fn';
import {isFunction} from './internal/is';
import {invalidArgument} from './internal/throw';
import {throwInvalidArgument} from './internal/throw';

export function Attempt(fn){
this._fn = fn;
}

Attempt.prototype = Object.create(Core);

Attempt.prototype._fork = function Attempt$fork(rej, res){
Attempt.prototype._interpret = function Attempt$interpret(rec, rej, res){
var r;
try{ r = this._fn() }catch(e){ rej(e); return noop }
res(r);
Expand All @@ -21,6 +21,6 @@ Attempt.prototype.toString = function Attempt$toString(){
};

export function attempt(f){
if(!isFunction(f)) invalidArgument('Future.try', 0, 'be a function', f);
if(!isFunction(f)) throwInvalidArgument('Future.try', 0, 'be a function', f);
return new Attempt(f);
}
38 changes: 25 additions & 13 deletions src/cache.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import {Core, isFuture} from './core';
import {noop} from './internal/fn';
import {invalidFuture} from './internal/throw';
import {noop, show} from './internal/fn';
import {throwInvalidFuture} from './internal/throw';
import {someError} from './internal/error';

var Cold = Cached.Cold = 0;
var Pending = Cached.Pending = 1;
var Rejected = Cached.Rejected = 2;
var Resolved = Cached.Resolved = 3;
var Crashed = Cached.Crashed = 2;
var Rejected = Cached.Rejected = 3;
var Resolved = Cached.Resolved = 4;

export function Queued(rej, res){
export function Queued(rec, rej, res){
this[Crashed] = rec;
this[Rejected] = rej;
this[Resolved] = res;
}
Expand Down Expand Up @@ -35,10 +38,10 @@ Cached.prototype.extractRight = function Cached$extractRight(){
return this.isResolved() ? [this._value] : [];
};

Cached.prototype._addToQueue = function Cached$addToQueue(rej, res){
Cached.prototype._addToQueue = function Cached$addToQueue(rec, rej, res){
var _this = this;
if(_this._state > Pending) return noop;
var i = _this._queue.push(new Queued(rej, res)) - 1;
var i = _this._queue.push(new Queued(rec, rej, res)) - 1;
_this._queued = _this._queued + 1;

return function Cached$removeFromQueue(){
Expand Down Expand Up @@ -66,6 +69,13 @@ Cached.prototype._drainQueue = function Cached$drainQueue(){
this._queued = 0;
};

Cached.prototype.crash = function Cached$crash(error){
if(this._state > Pending) return;
this._value = someError('Future.cache was running the cached Future', error, show(this._pure));
this._state = Crashed;
this._drainQueue();
};

Cached.prototype.reject = function Cached$reject(reason){
if(this._state > Pending) return;
this._value = reason;
Expand All @@ -84,30 +94,32 @@ Cached.prototype.run = function Cached$run(){
var _this = this;
if(_this._state > Cold) return;
_this._state = Pending;
_this._cancel = _this._pure._fork(
_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) }
);
};

Cached.prototype.reset = function Cached$reset(){
if(this._state === Cold) return;
if(this._state > Pending) this._cancel();
if(this._state === Pending) this._cancel();
this._cancel = noop;
this._queue = [];
this._queued = 0;
this._value = undefined;
this._state = Cold;
};

Cached.prototype._fork = function Cached$_fork(rej, res){
Cached.prototype._interpret = function Cached$interpret(rec, rej, res){
var cancel = noop;

switch(this._state){
case Pending: cancel = this._addToQueue(rej, res); break;
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(rej, res); this.run();
default: cancel = this._addToQueue(rec, rej, res); this.run();
}

return cancel;
Expand All @@ -118,6 +130,6 @@ Cached.prototype.toString = function Cached$toString(){
};

export function cache(m){
if(!isFuture(m)) invalidFuture('Future.cache', 0, m);
if(!isFuture(m)) throwInvalidFuture('Future.cache', 0, m);
return new Cached(m);
}
27 changes: 16 additions & 11 deletions src/chain-rec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {Core} from './core';
import {Next, Done} from './internal/iteration';
import {Undetermined, Synchronous, Asynchronous} from './internal/timing';
import {show, showf, noop} from './internal/fn';
import {someError} from './internal/error';

export function ChainRec(step, init){
this._step = step;
Expand All @@ -10,7 +11,7 @@ export function ChainRec(step, init){

ChainRec.prototype = Object.create(Core);

ChainRec.prototype._fork = function ChainRec$_fork(rej, res){
ChainRec.prototype._interpret = function ChainRec$interpret(rec, rej, res){

var _step = this._step;
var _init = this._init;
Expand All @@ -22,18 +23,22 @@ ChainRec.prototype._fork = function ChainRec$_fork(rej, res){
}

function drain(){
while(!state.done){
timing = Undetermined;
var m = _step(Next, Done, state.value);
cancel = m._fork(rej, resolved);

if(timing !== Synchronous){
timing = Asynchronous;
return;
try{
while(!state.done){
timing = Undetermined;
var m = _step(Next, Done, state.value);
cancel = m._interpret(rec, rej, resolved);

if(timing !== Synchronous){
timing = Asynchronous;
return;
}
}
}

res(state.value);
res(state.value);
}catch(e){
rec(someError('Future.chainRec was calling its iterator', e));
}
}

drain();
Expand Down
Loading