From 733c89bb4bf69a8baba9c87217dfa4150da98632 Mon Sep 17 00:00:00 2001 From: Aldwin Vlasblom Date: Thu, 1 Jun 2017 13:31:02 +0200 Subject: [PATCH] Add Future.done() and Future#done() Closes #79 "for real this time" --- README.md | 22 +++++++++++++ src/core.js | 7 ++++ src/dispatchers.js | 1 + src/dispatchers/done.js | 15 +++++++++ test/1.future.test.js | 73 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 118 insertions(+) create mode 100644 src/dispatchers/done.js diff --git a/README.md b/README.md index fb2ad80f..33132405 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,7 @@ getPackageName('package.json') 1. [Consuming Futures](#consuming-futures) * [fork](#fork) * [value](#value) + * [done](#done) * [promise](#promise) 1. [Parallelism](#parallelism) * [race](#race) @@ -828,6 +829,27 @@ Future.after(300, 'hello').value(console.log)(); //Nothing will happen. The Future was cancelled before it could settle. ``` +#### done +##### `#done :: Future a b ~> Nodeback a b -> Cancel` +##### `.done :: Nodeback a b -> Future a b -> Cancel` + +Fork the Future into a [Nodeback](#types). + +```js +Future.of('hello').done((err, val) => console.log(val)); +//> "hello" +``` + +This is like [fork](#fork), but instead of taking two unary functions, it takes +a single binary function. As with `fork()`, `done()` returns [`Cancel`](#types): + +```js +const m = Future.after(300, 'hello'); +const cancel = m.done((err, val) => console.log(val)); +cancel(); +//Nothing will happen. The Future was cancelled before it could settle. +``` + #### promise ##### `#promise :: Future a b ~> Promise b a` ##### `.promise :: Future a b -> Promise b a` diff --git a/src/core.js b/src/core.js index 0062a77b..8a87d4ae 100644 --- a/src/core.js +++ b/src/core.js @@ -119,6 +119,13 @@ Future.prototype.value = function Future$value(res){ return this._fork(throwRejection, res); }; +Future.prototype.done = function Future$done(callback){ + if(!isFuture(this)) invalidContext('Future#done', this); + if(!isFunction(callback)) invalidArgument('Future#done', 0, 'to be a Function', callback); + return this._fork(function Future$done$rej(x){ callback(x) }, + function Future$done$res(x){ callback(null, x) }); +}; + Future.prototype.promise = function Future$promise(){ return new Promise((res, rej) => this._fork(rej, res)); }; diff --git a/src/dispatchers.js b/src/dispatchers.js index b09b6abd..7602fec2 100644 --- a/src/dispatchers.js +++ b/src/dispatchers.js @@ -15,6 +15,7 @@ export {race} from './dispatchers/race'; export {swap} from './dispatchers/swap'; export {fold} from './dispatchers/fold'; +export {done} from './dispatchers/done'; export {fork} from './dispatchers/fork'; export {promise} from './dispatchers/promise'; export {value} from './dispatchers/value'; diff --git a/src/dispatchers/done.js b/src/dispatchers/done.js new file mode 100644 index 00000000..77f8bdf8 --- /dev/null +++ b/src/dispatchers/done.js @@ -0,0 +1,15 @@ +import {isFuture} from '../core'; +import {partial1} from '../internal/fn'; +import {isFunction} from '../internal/is'; +import {invalidArgument, invalidFuture} from '../internal/throw'; + +function done$callback(callback, m){ + if(!isFuture(m)) invalidFuture('Future.done', 1, m); + return m.done(callback); +} + +export function done(callback, m){ + if(!isFunction(callback)) invalidArgument('Future.done', 0, 'be a Function', callback); + if(arguments.length === 1) return partial1(done$callback, callback); + return done$callback(callback, m); +} diff --git a/test/1.future.test.js b/test/1.future.test.js index 1869672f..eb18cf11 100644 --- a/test/1.future.test.js +++ b/test/1.future.test.js @@ -7,6 +7,7 @@ import { isFuture, fork, value, + done, promise, seq, Par, @@ -103,6 +104,36 @@ describe('Future', () => { }); + describe('.done()', () => { + + it('is a curried binary function', () => { + expect(done).to.be.a('function'); + expect(done.length).to.equal(2); + expect(done(U.noop)).to.be.a('function'); + }); + + it('throws when not given a Function as first argument', () => { + const f = () => done(1); + expect(f).to.throw(TypeError, /Future.*first/); + }); + + it('throws when not given a Future as second argument', () => { + const f = () => done(U.add(1), 1); + expect(f).to.throw(TypeError, /Future.*second/); + }); + + it('dispatches to #done()', fin => { + const a = () => {}; + const mock = Object.create(F.mock); + mock.done = x => { + expect(x).to.equal(a); + fin(); + }; + done(a, mock); + }); + + }); + describe('.promise()', () => { it('throws when not given a Future', () => { @@ -239,6 +270,48 @@ describe('Future', () => { }); + describe('#done()', () => { + + it('throws when invoked out of context', () => { + const f = () => F.mock.done.call(null, U.noop); + expect(f).to.throw(TypeError, /Future/); + }); + + it('throws TypeError when not given a function', () => { + const xs = [NaN, {}, [], 1, 'a', new Date, undefined, null]; + const fs = xs.map(x => () => F.mock.done(x)); + fs.forEach(f => expect(f).to.throw(TypeError, /Future/)); + }); + + it('passes the rejection value as first parameter', fin => { + const mock = Object.create(Future.prototype); + mock._fork = l => {l(1)}; + mock.done((x, y) => { + expect(x).to.equal(1); + expect(y).to.equal(undefined); + fin(); + }); + }); + + it('passes the resolution value as second parameter', fin => { + const mock = Object.create(Future.prototype); + mock._fork = (l, r) => {r(1)}; + mock.done((x, y) => { + expect(x).to.equal(null); + expect(y).to.equal(1); + fin(); + }); + }); + + it('returns the return done of #_fork()', () => { + const mock = Object.create(Future.prototype); + const sentinel = {}; + mock._fork = () => sentinel; + expect(mock.done(U.noop)).to.equal(sentinel); + }); + + }); + describe('#promise()', () => { it('returns a Promise', () => {