Skip to content
Closed
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
46 changes: 19 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ for sponsoring the project.
- [`ap`: Combine the success values of multiple Futures using a function](#ap)
- [`and`: Logical *and* for Futures](#and)
- [`or`: Logical *or* for Futures](#or)
- [`finally`: Run a Future after the previous settles](#finally)
- [`assume`: Continue with another Future after the previous settles](#assume)
- [`race`: Race two Futures against each other](#race)
- [`both`: Await both success values from two Futures](#both)
- [`parallel`: Await all success values from many Futures](#parallel)
Expand Down Expand Up @@ -1036,7 +1036,7 @@ Returns a new Future which either rejects with the first rejection reason, or
resolves with the last resolution value once and if both Futures resolve. We
can use it if we want a computation to run only after another has succeeded.

See also [`or`](#or) and [`finally`](#finally).
See also [`or`](#or) and [`assume`](#assume).

```js
Future.after(300, null)
Expand Down Expand Up @@ -1072,7 +1072,7 @@ Returns a new Future which either resolves with the first resolution value, or
rejects with the last rejection value once and if both Futures reject. We can
use it if we want a computation to run only if another has failed.

See also [`and`](#and) and [`finally`](#finally).
See also [`and`](#and) and [`assume`](#assume).

```js
Future.rejectAfter(300, new Error('Failed'))
Expand All @@ -1091,53 +1091,45 @@ any([Future.reject(1), Future.after(20, 2), Future.of(3)]).value(console.log);
//> 2
```

#### finally
#### assume

<details><summary><code>finally :: Future a c -> Future a b -> Future a b</code></summary>
<details><summary><code>assume :: Future c d -> Future a b -> Future c d</code></summary>

```hs
finally :: Future a c -> Future a b -> Future a b
lastly :: Future a c -> Future a b -> Future a b
Future.prototype.finally :: Future a b ~> Future a c -> Future a b
Future.prototype.lastly :: Future a b ~> Future a c -> Future a b
assume :: Future c d -> Future a b -> Future c d
Future.prototype.assume :: Future a b ~> Future c d -> Future c d
```

</details>

Run a second Future after the first settles (successfully or unsuccessfully).
Rejects with the rejection reason from the first or second Future, or resolves
with the resolution value from the first Future. We can use this when we want
a computation to run after another settles, successfully or unsuccessfully.

If you're looking to clean up resources after running a computation which
acquires them, you should use [`hook`](#hook), which has many more fail-safes
in place.

This function has an alias `lastly`, for environments where `finally` is
reserved.
Run a second Future after the first settles (successfully or unsuccessfully),
assuming its state.

See also [`and`](#and) and [`or`](#or).

```js
Future.of('Hello')
.finally(Future.of('All done!').map(console.log))
.assume(Future.of('World!'))
.fork(console.error, console.log);
//> "All done!"
//> "Hello"
//> "World!"

Future.reject('Hello')
.assume(Future.of('World!'))
.fork(console.error, console.log);
//> "World!"
```

Note that the *first* Future is given as the *last* argument to `Future.finally()`:
Note that the *first* Future is given as the *last* argument to `Future.assume()`:

```js
var program = S.pipe([
Future.of,
Future.finally(Future.of('All done!').map(console.log)),
Future.assume(Future.of('World!')),
Future.fork(console.error, console.log)
]);

program('Hello');
//> "All done!"
//> "Hello"
//> "World!"
```

### Consuming Futures
Expand Down
13 changes: 5 additions & 8 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ declare module 'fluture' {
/** Attempt to extract the resolution value. See https://github.com/fluture-js/Fluture#extractright */
extractRight(): Array<R>

/** Set up a cleanup Future to run after this one is done. See https://github.com/fluture-js/Fluture#finally */
finally(cleanup: FutureInstance<L, any>): FutureInstance<L, R>
/** Continue with another Future after this one settles. See https://github.com/fluture-js/Fluture#assume */
assume<LB, RB>(other: FutureInstance<LB, RB>): FutureInstance<LB, RB>

/** Fold both branches into the resolution branch. See https://github.com/fluture-js/Fluture#fold */
fold<L, RB>(lmapper: (reason: L) => RB, rmapper: (value: R) => RB): FutureInstance<L, RB>
Expand All @@ -99,9 +99,6 @@ declare module 'fluture' {
/** Fork with exception recovery. See https://github.com/fluture-js/Fluture#forkCatch */
forkCatch(recover: RecoverFunction, reject: RejectFunction<L>, resolve: ResolveFunction<R>): Cancel

/** Set up a cleanup Future to run after this one is done. See https://github.com/fluture-js/Fluture#finally */
lastly(cleanup: FutureInstance<L, any>): FutureInstance<L, R>

/** Map over the resolution value in this Future. See https://github.com/fluture-js/Fluture#map */
map<RB>(mapper: (value: R) => RB): FutureInstance<L, RB>

Expand Down Expand Up @@ -254,9 +251,9 @@ declare module 'fluture' {
/** Returns true for Futures that will certainly never settle. See https://github.com/fluture-js/Fluture#isnever */
export function isNever(value: any): boolean

/** Set up a cleanup Future to run after the given action has settled. See https://github.com/fluture-js/Fluture#lastly */
export function lastly<L, R>(cleanup: FutureInstance<L, any>, action: FutureInstance<L, R>): FutureInstance<L, R>
export function lastly<L, R>(cleanup: FutureInstance<L, any>): (action: FutureInstance<L, R>) => FutureInstance<L, R>
/** Continue with another Future after the previous settles. See https://github.com/fluture-js/Fluture#assume */
export function assume<L, R>(other: FutureInstance<L, R>, source: FutureInstance<any, any>): FutureInstance<L, R>
export function assume<L, R>(other: FutureInstance<L, R>): (source: FutureInstance<any, any>) => FutureInstance<L, R>

/** Map over the resolution value of the given Future. See https://github.com/fluture-js/Fluture#map */
export function map<L, RA, RB>(mapper: (value: RA) => RB, source: FutureInstance<L, RA>): FutureInstance<L, RB>
Expand Down
14 changes: 14 additions & 0 deletions src/dispatchers/assume.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {isFuture} from '../future';
import {partial1} from '../internal/utils';
import {throwInvalidFuture} from '../internal/throw';

function assume$right(right, left){
if(!isFuture(left)) throwInvalidFuture('Future.assume', 1, left);
return left.assume(right);
}

export function assume(right, left){
if(!isFuture(right)) throwInvalidFuture('Future.assume', 0, right);
if(arguments.length === 1) return partial1(assume$right, right);
return assume$right(right, left);
}
2 changes: 1 addition & 1 deletion src/dispatchers/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export {chain} from './chain';

export {mapRej} from './map-rej';
export {chainRej} from './chain-rej';
export {lastly, lastly as finally} from './lastly';
export {assume} from './assume';

export {and} from './and';
export {both} from './both';
Expand Down
14 changes: 0 additions & 14 deletions src/dispatchers/lastly.mjs

This file was deleted.

14 changes: 4 additions & 10 deletions src/future.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -616,16 +616,10 @@ defineBimapperAction('fold', {
rejected: function FoldAction$rejected(x){ return mapWith(this.lmapper, resolve, x) }
});

var finallyAction = {
cancel: function FinallyAction$cancel(){ this.other._interpret(noop, noop, noop)() },
rejected: function FinallyAction$rejected(x){ return this.other._and(new Rejected(x)) },
resolved: function FinallyAction$resolved(x){
return this.other._map(function FoldAction$resolved$mapper(){ return x });
}
};

defineOtherAction('finally', finallyAction);
defineOtherAction('lastly', finallyAction);
defineOtherAction('assume', {
rejected: returnOther,
resolved: returnOther
});

defineOtherAction('and', {
resolved: returnOther
Expand Down
2 changes: 1 addition & 1 deletion test.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import './test/5.chain.test.mjs';
import './test/5.encase-n.test.mjs';
import './test/5.encase-p.test.mjs';
import './test/5.encase.test.mjs';
import './test/5.finally.test.mjs';
import './test/5.assume.test.mjs';
import './test/5.fold.test.mjs';
import './test/5.hook.test.mjs';
import './test/5.map-rej.test.mjs';
Expand Down
8 changes: 4 additions & 4 deletions test/2.transformation.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -340,9 +340,9 @@ describe('Transformation', function (){

});

describe('finally', function (){
describe('assume', function (){

var seq = dummy.finally(dummy);
var seq = dummy.assume(dummy);

describe('#_interpret()', function (){

Expand All @@ -352,7 +352,7 @@ describe('Transformation', function (){

it('runs the other if the left rejects', function (done){
var other = Future(function (){done()});
var m = new Transformation(rejected, nil).finally(other);
var m = new Transformation(rejected, nil).assume(other);
m._interpret(done, noop, noop);
});

Expand All @@ -361,7 +361,7 @@ describe('Transformation', function (){
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"))');
expect(seq.toString()).to.equal('Future.of("resolved").assume(Future.of("resolved"))');
});

});
Expand Down
109 changes: 109 additions & 0 deletions test/5.assume.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import chai from 'chai';
import {Future, assume, of, reject} from '../index.mjs';
import * as U from './util';
import * as F from './futures';
import type from 'sanctuary-type-identifiers';

var expect = chai.expect;

var testInstance = function (assume){

it('is considered a member of fluture/Fluture', function (){
expect(type(assume(of(1), of(2)))).to.equal(Future['@@type']);
});

describe('#_interpret()', function (){

it('runs the second Future when the first resolves', function (done){
assume(of(1), of(null).map(done))._interpret(done, U.noop, U.noop);
});

it('runs the second Future when the first rejects', function (done){
assume(reject(1), of(null).map(done))._interpret(done, U.noop, U.noop);
});

it('always rejects with the rejection reason of the second', function (){
var actualResolved = assume(of(1), reject(2));
var actualRejected = assume(reject(1), reject(2));
return Promise.all([
U.assertRejected(actualResolved, 2),
U.assertRejected(actualRejected, 2)
]);
});

it('always resolves with the resolution value of the second', function (){
var actualResolved = assume(of(1), of(2));
var actualRejected = assume(reject(1), of(2));
return Promise.all([
U.assertResolved(actualResolved, 2),
U.assertResolved(actualRejected, 2)
]);
});

it('does nothing after being cancelled', function (done){
assume(F.resolvedSlow, F.resolved)._interpret(done, U.failRej, U.failRes)();
assume(F.resolved, F.resolvedSlow)._interpret(done, U.failRej, U.failRes)();
assume(F.rejectedSlow, F.rejected)._interpret(done, U.failRej, U.failRes)();
assume(F.rejected, F.rejectedSlow)._interpret(done, U.failRej, U.failRes)();
setTimeout(done, 25);
});

});

};

describe('assume()', function (){

it('is a curried binary function', function (){
expect(assume).to.be.a('function');
expect(assume.length).to.equal(2);
expect(assume(of(1))).to.be.a('function');
});

it('throws when not given a Future as first argument', function (){
var f = function (){ return assume(1) };
expect(f).to.throw(TypeError, /Future.*first/);
});

it('throws when not given a Future as second argument', function (){
var f = function (){ return assume(of(1), 1) };
expect(f).to.throw(TypeError, /Future.*second/);
});

testInstance(function (a, b){ return assume(b, a) });

});

describe('Future#assume()', function (){

it('throws when invoked out of context', function (){
var f = function (){ return of(1).assume.call(null, of(1)) };
expect(f).to.throw(TypeError, /Future/);
});

it('throws TypeError when not given a Future', function (){
var xs = [NaN, {}, [], 1, 'a', new Date, undefined, null, function (x){ return x }];
var fs = xs.map(function (x){ return function (){ return of(1).assume(x) } });
fs.forEach(function (f){ return expect(f).to.throw(TypeError, /Future/) });
});

testInstance(function (a, b){ return a.assume(b) });

});

describe('Future#assume()', function (){

it('throws when invoked out of context', function (){
var f = function (){ return of(1).assume.call(null, of(1)) };
expect(f).to.throw(TypeError, /Future/);
});

it('throws TypeError when not given a Future', function (){
var xs = [NaN, {}, [], 1, 'a', new Date, undefined, null, function (x){ return x }];
var fs = xs.map(function (x){ return function (){ return of(1).assume(x) } });
fs.forEach(function (f){ return expect(f).to.throw(TypeError, /Future/) });
});

testInstance(function (a, b){ return a.assume(b) });

});
Loading