diff --git a/README.md b/README.md index 3ac1bfbe..8195220b 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ For front-end applications and node Promise e r) -> a -> Future e r` -##### `.fromPromise2 :: (a, b -> Promise e r) -> a -> b -> Future e r` -##### `.fromPromise3 :: (a, b, c -> Promise e r) -> a -> b -> c -> Future e r` +#### encaseP +##### `.tryP` :: (a -> Promise e r) -> Future e r +##### `.encaseP :: (a -> Promise e r) -> a -> Future e r` +##### `.encaseP2 :: (a, b -> Promise e r) -> a -> b -> Future e r` +##### `.encaseP3 :: (a, b, c -> Promise e r) -> a -> b -> c -> Future e r` Allows Promise-returning functions to be turned into Future-returning functions. -Takes a function which returns a Promise, and a value, and returns a Future -which calls the function to produce the Promise, and resolves with the Promise -resolution value, or rejects with the Promise rejection reason. +Takes a function which returns a Promise, and a value, and returns a Future. +When forked, the Future calls the function with the value to produce the Promise, +and resolves with its resolution value, or rejects with its rejection reason. ```js -const fetchf = Future.fromPromise(fetch); +const fetchf = Future.encaseP(fetch); fetchf('https://api.github.com/users/Avaq') -.chain(res => Future.fromPromise(_ => res.json(), 0)) +.chain(res => Future.tryP(_ => res.json())) .map(user => user.name) .fork(console.error, console.log); //> "Aldwin Vlasblom" ``` -Furthermore; `fromPromise2` and `fromPromise3` are binary and ternary versions -of `fromPromise`, applying two or three arguments to the given function respectively. +Furthermore; `encaseP2` and `encaseP3` are binary and ternary versions +of `encaseP`, applying two or three arguments to the given function respectively. #### node ##### `.node :: (((a, b) -> ()) -> ()) -> Future a b` diff --git a/index.es.js b/index.es.js index cad01da5..487ecafd 100644 --- a/index.es.js +++ b/index.es.js @@ -14,7 +14,7 @@ export {after} from './src/after'; export {cache} from './src/cache'; export {chainRec} from './src/chain-rec'; export {encase, encase2, encase3, attempt, attempt as try} from './src/encase'; -export {fromPromise, fromPromise2, fromPromise3} from './src/from-promise'; +export {encaseP, encaseP2, encaseP3, tryP} from './src/encase-p'; export {go, go as do} from './src/go'; export {hook} from './src/hook'; export {node} from './src/node'; diff --git a/src/encase-p.js b/src/encase-p.js new file mode 100644 index 00000000..0fb8fccf --- /dev/null +++ b/src/encase-p.js @@ -0,0 +1,93 @@ +import {Core} from './core'; +import {noop, show, showf, partial1, partial2, partial3} from './internal/fn'; +import {isThenable, isFunction} from './internal/is'; +import {invalidArgument, typeError} from './internal/throw'; + +function escape(f){ + return function imprisoned(x){ + setTimeout(function escaped(){ f(x) }, 0); + }; +} + +function check$promise(p, f, a, b, c){ + return isThenable(p) ? p : typeError( + 'Future.encaseP expects the function its given to return a Promise/Thenable' + + `\n Actual: ${show(p)}\n From calling: ${showf(f)}` + + `\n With a: ${show(a)}` + + (arguments.length > 3 ? `\n With b: ${show(b)}` : '') + + (arguments.length > 4 ? `\n With c: ${show(c)}` : '') + ); +} + +function EncaseP$0$fork(rej, res){ + const {_fn} = this; + check$promise(_fn(), _fn).then(escape(res), escape(rej)); + return noop; +} + +function EncaseP$1$fork(rej, res){ + const {_fn, _a} = this; + check$promise(_fn(_a), _fn, _a).then(escape(res), escape(rej)); + return noop; +} + +function EncaseP$2$fork(rej, res){ + const {_fn, _a, _b} = this; + check$promise(_fn(_a, _b), _fn, _a, _b).then(escape(res), escape(rej)); + return noop; +} + +function EncaseP$3$fork(rej, res){ + const {_fn, _a, _b, _c} = this; + check$promise(_fn(_a, _b, _c), _fn, _a, _b, _c).then(escape(res), escape(rej)); + return noop; +} + +const forks = [EncaseP$0$fork, EncaseP$1$fork, EncaseP$2$fork, EncaseP$3$fork]; + +function EncaseP(fn, a, b, c){ + this._length = arguments.length - 1; + this._fn = fn; + this._a = a; + this._b = b; + this._c = c; + this._fork = forks[this._length]; +} + +EncaseP.prototype = Object.create(Core); + +EncaseP.prototype.toString = function EncaseP$toString(){ + const args = [this._a, this._b, this._c].slice(0, this._length).map(show).join(', '); + const name = `encaseP${this._length > 1 ? this._length : ''}`; + return `Future.${name}(${show(this._fn)}, ${args})`; +}; + +export function tryP(f){ + if(!isFunction(f)) invalidArgument('Future.tryP', 0, 'be a function', f); + return new EncaseP(f); +} + +export function encaseP(f, x){ + if(!isFunction(f)) invalidArgument('Future.encaseP', 0, 'be a function', f); + if(arguments.length === 1) return partial1(encaseP, f); + return new EncaseP(f, x); +} + +export function encaseP2(f, x, y){ + if(!isFunction(f)) invalidArgument('Future.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 EncaseP(f, x, y); + } +} + +export function encaseP3(f, x, y, z){ + if(!isFunction(f)) invalidArgument('Future.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 EncaseP(f, x, y, z); + } +} diff --git a/src/from-promise.js b/src/from-promise.js deleted file mode 100644 index b5d98c07..00000000 --- a/src/from-promise.js +++ /dev/null @@ -1,76 +0,0 @@ -import {Core} from './core'; -import {noop, show, showf, partial1, partial2, partial3} from './internal/fn'; -import {isThenable, isFunction} from './internal/is'; -import {invalidArgument, typeError} from './internal/throw'; - -function check$promise(p, f, a, b, c){ - return isThenable(p) ? p : typeError( - 'Future.fromPromise expects the function its given to return a Promise/Thenable' - + `\n Actual: ${show(p)}\n From calling: ${showf(f)}` - + `\n With a: ${show(a)}` - + (arguments.length > 3 ? `\n With b: ${show(b)}` : '') - + (arguments.length > 4 ? `\n With c: ${show(c)}` : '') - ); -} - -function FromPromise$1$fork(rej, res){ - const {_fn, _a} = this; - check$promise(_fn(_a), _fn, _a).then(res, rej); - return noop; -} - -function FromPromise$2$fork(rej, res){ - const {_fn, _a, _b} = this; - check$promise(_fn(_a, _b), _fn, _a, _b).then(res, rej); - return noop; -} - -function FromPromise$3$fork(rej, res){ - const {_fn, _a, _b, _c} = this; - check$promise(_fn(_a, _b, _c), _fn, _a, _b, _c).then(res, rej); - return noop; -} - -const forks = [noop, FromPromise$1$fork, FromPromise$2$fork, FromPromise$3$fork]; - -function FromPromise(fn, a, b, c){ - this._length = arguments.length - 1; - this._fn = fn; - this._a = a; - this._b = b; - this._c = c; - this._fork = forks[this._length]; -} - -FromPromise.prototype = Object.create(Core); - -FromPromise.prototype.toString = function FromPromise$toString(){ - const args = [this._a, this._b, this._c].slice(0, this._length).map(show).join(', '); - const name = `fromPromise${this._length > 1 ? this._length : ''}`; - return `Future.${name}(${show(this._fn)}, ${args})`; -}; - -export function fromPromise(f, x){ - if(!isFunction(f)) invalidArgument('Future.fromPromise', 0, 'be a function', f); - if(arguments.length === 1) return partial1(fromPromise, f); - return new FromPromise(f, x); -} - -export function fromPromise2(f, x, y){ - if(!isFunction(f)) invalidArgument('Future.fromPromise2', 0, 'be a function', f); - switch(arguments.length){ - case 1: return partial1(fromPromise2, f); - case 2: return partial2(fromPromise2, f, x); - default: return new FromPromise(f, x, y); - } -} - -export function fromPromise3(f, x, y, z){ - if(!isFunction(f)) invalidArgument('Future.fromPromise3', 0, 'be a function', f); - switch(arguments.length){ - case 1: return partial1(fromPromise3, f); - case 2: return partial2(fromPromise3, f, x); - case 3: return partial3(fromPromise3, f, x, y); - default: return new FromPromise(f, x, y, z); - } -} diff --git a/src/index.js b/src/index.js index efa75dae..418e0f75 100644 --- a/src/index.js +++ b/src/index.js @@ -13,7 +13,7 @@ import {after} from './after'; import {cache} from './cache'; import {chainRec} from './chain-rec'; import {encase, encase2, encase3, attempt} from './encase'; -import {fromPromise, fromPromise2, fromPromise3} from './from-promise'; +import {encaseP, encaseP2, encaseP3, tryP} from './encase-p'; import {go} from './go'; import {hook} from './hook'; import {node} from './node'; @@ -36,9 +36,10 @@ export default Object.assign(Future, dispatchers, { isNever, after, cache, - fromPromise, - fromPromise2, - fromPromise3, + encaseP, + encaseP2, + encaseP3, + tryP, hook, node, Par, diff --git a/test/5.encase-p.test.js b/test/5.encase-p.test.js new file mode 100644 index 00000000..a86d84b8 --- /dev/null +++ b/test/5.encase-p.test.js @@ -0,0 +1,202 @@ +import {expect} from 'chai'; +import {Future, encaseP, encaseP2, encaseP3, tryP} from '../index.es.js'; +import * as U from './util'; +import type from 'sanctuary-type-identifiers'; + +const unaryNoop = a => Promise.resolve(a); +const binaryNoop = (a, b) => Promise.resolve(b); +const ternaryNoop = (a, b, c) => Promise.resolve(c); + +describe('tryP()', () => { + + it('throws TypeError when not given a function', () => { + const xs = [NaN, {}, [], 1, 'a', new Date, undefined, null]; + const fs = xs.map(x => () => tryP(x)); + fs.forEach(f => expect(f).to.throw(TypeError, /Future/)); + }); + + it('returns an instance of Future', () => { + expect(tryP(unaryNoop)).to.be.an.instanceof(Future); + }); + +}); + +describe('encaseP()', () => { + + it('is a curried binary function', () => { + expect(encaseP).to.be.a('function'); + expect(encaseP.length).to.equal(2); + expect(encaseP(U.noop)).to.be.a('function'); + }); + + it('throws TypeError when not given a function', () => { + const xs = [NaN, {}, [], 1, 'a', new Date, undefined, null]; + const fs = xs.map(x => () => encaseP(x)); + fs.forEach(f => expect(f).to.throw(TypeError, /Future/)); + }); + + it('returns an instance of Future', () => { + expect(encaseP(unaryNoop, null)).to.be.an.instanceof(Future); + }); + +}); + +describe('encaseP2()', () => { + + it('is a curried ternary function', () => { + expect(encaseP2).to.be.a('function'); + expect(encaseP2.length).to.equal(3); + expect(encaseP2((a, b) => b)).to.be.a('function'); + expect(encaseP2((a, b) => b)(1)).to.be.a('function'); + expect(encaseP2((a, b) => b, 1)).to.be.a('function'); + }); + + it('throws TypeError when not given a function', () => { + const xs = [NaN, {}, [], 1, 'a', new Date, undefined, null]; + const fs = xs.map(x => () => encaseP2(x)); + fs.forEach(f => expect(f).to.throw(TypeError, /Future/)); + }); + + it('returns an instance of Future', () => { + expect(encaseP2(binaryNoop, null, null)).to.be.an.instanceof(Future); + }); + +}); + +describe('encaseP3()', () => { + + it('is a curried quaternary function', () => { + expect(encaseP3).to.be.a('function'); + expect(encaseP3.length).to.equal(4); + expect(encaseP3((a, b, c) => c)).to.be.a('function'); + expect(encaseP3((a, b, c) => c)(1)).to.be.a('function'); + expect(encaseP3((a, b, c) => c, 1)).to.be.a('function'); + expect(encaseP3((a, b, c) => c)(1)(2)).to.be.a('function'); + expect(encaseP3((a, b, c) => c, 1)(2)).to.be.a('function'); + expect(encaseP3((a, b, c) => c)(1, 2)).to.be.a('function'); + expect(encaseP3((a, b, c) => c, 1, 2)).to.be.a('function'); + }); + + it('throws TypeError when not given a function', () => { + const xs = [NaN, {}, [], 1, 'a', new Date, undefined, null]; + const fs = xs.map(x => () => encaseP3(x)); + fs.forEach(f => expect(f).to.throw(TypeError, /Future/)); + }); + + it('returns an instance of Future', () => { + expect(encaseP3(ternaryNoop, null, null, null)) + .to.be.an.instanceof(Future); + }); + +}); + +describe('FromPromise', () => { + + it('extends Future', () => { + expect(encaseP(U.noop, 1)).to.be.an.instanceof(Future); + }); + + it('is considered a member of fluture/Fluture', () => { + expect(type(encaseP(U.noop, 1))).to.equal(Future['@@type']); + }); + + describe('#fork()', () => { + + describe('(nullary)', () => { + + it('throws TypeError when the function does not return a Promise', () => { + const f = () => tryP(U.noop).fork(U.noop, U.noop); + expect(f).to.throw(TypeError, /Future.*Promise/); + }); + + it('resolves with the resolution value of the returned Promise', () => { + const actual = tryP(_ => Promise.resolve(1)); + return U.assertResolved(actual, 1); + }); + + it('rejects with rejection reason of the returned Promise', () => { + const actual = tryP(_ => Promise.reject(U.error)); + return U.assertRejected(actual, U.error); + }); + + }); + + describe('(unary)', () => { + + it('throws TypeError when the function does not return a Promise', () => { + const f = () => encaseP(U.noop, 1).fork(U.noop, U.noop); + expect(f).to.throw(TypeError, /Future.*Promise/); + }); + + it('resolves with the resolution value of the returned Promise', () => { + const actual = encaseP(x => Promise.resolve(x + 1), 1); + return U.assertResolved(actual, 2); + }); + + it('rejects with rejection reason of the returned Promise', () => { + const actual = encaseP(_ => Promise.reject(U.error), 1); + return U.assertRejected(actual, U.error); + }); + + }); + + describe('(binary)', () => { + + it('throws TypeError when the function does not return a Promise', () => { + const f = () => encaseP2(U.noop, 1, 1).fork(U.noop, U.noop); + expect(f).to.throw(TypeError, /Future.*Promise/); + }); + + it('resolves with the resolution value of the returned Promise', () => { + const actual = encaseP2((x, y) => Promise.resolve(y + 1), 1, 1); + return U.assertResolved(actual, 2); + }); + + it('rejects with rejection reason of the returned Promise', () => { + const actual = encaseP2(_ => Promise.reject(U.error), 1, 1); + return U.assertRejected(actual, U.error); + }); + + }); + + describe('(ternary)', () => { + + it('throws TypeError when the function does not return a Promise', () => { + const f = () => encaseP3(U.noop, 1, 1, 1).fork(U.noop, U.noop); + expect(f).to.throw(TypeError, /Future.*Promise/); + }); + + it('resolves with the resolution value of the returned Promise', () => { + const actual = encaseP3((x, y, z) => Promise.resolve(z + 1), 1, 1, 1); + return U.assertResolved(actual, 2); + }); + + it('rejects with rejection reason of the returned Promise', () => { + const actual = encaseP3(_ => Promise.reject(U.error), 1, 1, 1); + return U.assertRejected(actual, U.error); + }); + + }); + + }); + + describe('#toString()', () => { + + it('returns the code to create the FromPromise', () => { + const m1 = encaseP(unaryNoop, null); + const m2 = encaseP2(binaryNoop, null, null); + const m3 = encaseP3(ternaryNoop, null, null, null); + expect(m1.toString()).to.equal( + `Future.encaseP(${unaryNoop.toString()}, null)` + ); + expect(m2.toString()).to.equal( + `Future.encaseP2(${binaryNoop.toString()}, null, null)` + ); + expect(m3.toString()).to.equal( + `Future.encaseP3(${ternaryNoop.toString()}, null, null, null)` + ); + }); + + }); + +}); diff --git a/test/5.from-promise.test.js b/test/5.from-promise.test.js deleted file mode 100644 index beedc030..00000000 --- a/test/5.from-promise.test.js +++ /dev/null @@ -1,169 +0,0 @@ -import {expect} from 'chai'; -import {Future, fromPromise, fromPromise2, fromPromise3} from '../index.es.js'; -import * as U from './util'; -import type from 'sanctuary-type-identifiers'; - -const unaryNoop = a => Promise.resolve(a); -const binaryNoop = (a, b) => Promise.resolve(b); -const ternaryNoop = (a, b, c) => Promise.resolve(c); - -describe('fromPromise()', () => { - - it('is a curried binary function', () => { - expect(fromPromise).to.be.a('function'); - expect(fromPromise.length).to.equal(2); - expect(fromPromise(U.noop)).to.be.a('function'); - }); - - it('throws TypeError when not given a function', () => { - const xs = [NaN, {}, [], 1, 'a', new Date, undefined, null]; - const fs = xs.map(x => () => fromPromise(x)); - fs.forEach(f => expect(f).to.throw(TypeError, /Future/)); - }); - - it('returns an instance of Future', () => { - expect(fromPromise(unaryNoop, null)).to.be.an.instanceof(Future); - }); - -}); - -describe('fromPromise2()', () => { - - it('is a curried ternary function', () => { - expect(fromPromise2).to.be.a('function'); - expect(fromPromise2.length).to.equal(3); - expect(fromPromise2((a, b) => b)).to.be.a('function'); - expect(fromPromise2((a, b) => b)(1)).to.be.a('function'); - expect(fromPromise2((a, b) => b, 1)).to.be.a('function'); - }); - - it('throws TypeError when not given a function', () => { - const xs = [NaN, {}, [], 1, 'a', new Date, undefined, null]; - const fs = xs.map(x => () => fromPromise2(x)); - fs.forEach(f => expect(f).to.throw(TypeError, /Future/)); - }); - - it('returns an instance of Future', () => { - expect(fromPromise2(binaryNoop, null, null)).to.be.an.instanceof(Future); - }); - -}); - -describe('fromPromise3()', () => { - - it('is a curried quaternary function', () => { - expect(fromPromise3).to.be.a('function'); - expect(fromPromise3.length).to.equal(4); - expect(fromPromise3((a, b, c) => c)).to.be.a('function'); - expect(fromPromise3((a, b, c) => c)(1)).to.be.a('function'); - expect(fromPromise3((a, b, c) => c, 1)).to.be.a('function'); - expect(fromPromise3((a, b, c) => c)(1)(2)).to.be.a('function'); - expect(fromPromise3((a, b, c) => c, 1)(2)).to.be.a('function'); - expect(fromPromise3((a, b, c) => c)(1, 2)).to.be.a('function'); - expect(fromPromise3((a, b, c) => c, 1, 2)).to.be.a('function'); - }); - - it('throws TypeError when not given a function', () => { - const xs = [NaN, {}, [], 1, 'a', new Date, undefined, null]; - const fs = xs.map(x => () => fromPromise3(x)); - fs.forEach(f => expect(f).to.throw(TypeError, /Future/)); - }); - - it('returns an instance of Future', () => { - expect(fromPromise3(ternaryNoop, null, null, null)) - .to.be.an.instanceof(Future); - }); - -}); - -describe('FromPromise', () => { - - it('extends Future', () => { - expect(fromPromise(U.noop, 1)).to.be.an.instanceof(Future); - }); - - it('is considered a member of fluture/Fluture', () => { - expect(type(fromPromise(U.noop, 1))).to.equal(Future['@@type']); - }); - - describe('#fork()', () => { - - describe('(unary)', () => { - - it('throws TypeError when the function does not return a Promise', () => { - const f = () => fromPromise(U.noop, 1).fork(U.noop, U.noop); - expect(f).to.throw(TypeError, /Future.*Promise/); - }); - - it('resolves with the resolution value of the returned Promise', () => { - const actual = fromPromise(x => Promise.resolve(x + 1), 1); - return U.assertResolved(actual, 2); - }); - - it('rejects with rejection reason of the returned Promise', () => { - const actual = fromPromise(_ => Promise.reject(U.error), 1); - return U.assertRejected(actual, U.error); - }); - - }); - - describe('(binary)', () => { - - it('throws TypeError when the function does not return a Promise', () => { - const f = () => fromPromise2(U.noop, 1, 1).fork(U.noop, U.noop); - expect(f).to.throw(TypeError, /Future.*Promise/); - }); - - it('resolves with the resolution value of the returned Promise', () => { - const actual = fromPromise2((x, y) => Promise.resolve(y + 1), 1, 1); - return U.assertResolved(actual, 2); - }); - - it('rejects with rejection reason of the returned Promise', () => { - const actual = fromPromise2(_ => Promise.reject(U.error), 1, 1); - return U.assertRejected(actual, U.error); - }); - - }); - - describe('(ternary)', () => { - - it('throws TypeError when the function does not return a Promise', () => { - const f = () => fromPromise3(U.noop, 1, 1, 1).fork(U.noop, U.noop); - expect(f).to.throw(TypeError, /Future.*Promise/); - }); - - it('resolves with the resolution value of the returned Promise', () => { - const actual = fromPromise3((x, y, z) => Promise.resolve(z + 1), 1, 1, 1); - return U.assertResolved(actual, 2); - }); - - it('rejects with rejection reason of the returned Promise', () => { - const actual = fromPromise3(_ => Promise.reject(U.error), 1, 1, 1); - return U.assertRejected(actual, U.error); - }); - - }); - - }); - - describe('#toString()', () => { - - it('returns the code to create the FromPromise', () => { - const m1 = fromPromise(unaryNoop, null); - const m2 = fromPromise2(binaryNoop, null, null); - const m3 = fromPromise3(ternaryNoop, null, null, null); - expect(m1.toString()).to.equal( - `Future.fromPromise(${unaryNoop.toString()}, null)` - ); - expect(m2.toString()).to.equal( - `Future.fromPromise2(${binaryNoop.toString()}, null, null)` - ); - expect(m3.toString()).to.equal( - `Future.fromPromise3(${ternaryNoop.toString()}, null, null, null)` - ); - }); - - }); - -});