├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ └── test.yml ├── .gitignore ├── .npmrc ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── bench ├── .eslintrc.json ├── .npmrc ├── README.md ├── fluture.js ├── folktale.js ├── package.json └── promise.js ├── dist ├── bundle.js └── module.js ├── index.cjs.js ├── index.d.ts ├── index.js ├── logo.png ├── package.json ├── rollup.config.dist.js ├── rollup.config.js ├── scripts ├── bench.js ├── distribute ├── test-esm └── test-mem ├── src ├── after.js ├── alt.js ├── and.js ├── ap.js ├── attempt-p.js ├── attempt.js ├── bichain.js ├── bimap.js ├── both.js ├── cache.js ├── chain-rej.js ├── chain.js ├── coalesce.js ├── done.js ├── encase-p.js ├── encase.js ├── extract-left.js ├── extract-right.js ├── fork-catch.js ├── fork.js ├── future.js ├── go.js ├── hook.js ├── internal │ ├── const.js │ ├── debug.js │ ├── error.js │ ├── iteration.js │ ├── list.js │ ├── parallel.js │ ├── predicates.js │ ├── timing.js │ └── utils.js ├── lastly.js ├── map-rej.js ├── map.js ├── node.js ├── pap.js ├── par.js ├── parallel.js ├── promise.js ├── race.js ├── reject-after.js ├── seq.js ├── swap.js └── value.js └── test ├── arbitraries.js ├── assertions.js ├── build ├── main.js └── require.js ├── integration └── main.js ├── prop ├── 0.algebra.js ├── 0.fantasy-land.js ├── 1.fantasy-libs.js └── 2.arbitrary.js ├── types └── map.test-d.ts ├── unit ├── 0.debug.js ├── 0.error.js ├── 0.iteration.js ├── 0.list.js ├── 0.predicates.js ├── 0.utils.js ├── 1.future.js ├── 1.is-future.js ├── 1.is-never.js ├── 1.never.js ├── 1.pipe.js ├── 2.done.js ├── 2.extract-left.js ├── 2.extract-right.js ├── 2.fork-catch.js ├── 2.fork.js ├── 2.promise.js ├── 2.value.js ├── 3.after.js ├── 3.attempt-p.js ├── 3.attempt.js ├── 3.cache.js ├── 3.crash.js ├── 3.encase-p.js ├── 3.encase.js ├── 3.go.js ├── 3.hook.js ├── 3.node.js ├── 3.parallel.js ├── 3.reject-after.js ├── 3.reject.js ├── 3.resolve.js ├── 4.alt.js ├── 4.and.js ├── 4.ap.js ├── 4.bichain.js ├── 4.bimap.js ├── 4.both.js ├── 4.chain-rej.js ├── 4.chain.js ├── 4.coalesce.js ├── 4.lastly.js ├── 4.map-rej.js ├── 4.map.js ├── 4.pap.js ├── 4.race.js ├── 4.swap.js ├── 5.chain-rec.js ├── 5.par.js └── 5.seq.js └── util ├── futures.js ├── props.js └── util.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /test/build/require.js 2 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": ["warp"], 4 | "parserOptions": { 5 | "ecmaVersion": 2015, 6 | "sourceType": "module" 7 | }, 8 | "globals": { 9 | "setTimeout": false, 10 | "clearTimeout": false, 11 | "Promise": false 12 | }, 13 | "rules": { 14 | "space-before-blocks": [2, "never"], 15 | "consistent-this": [2, "_this"], 16 | "semi": [2, "always", {"omitLastInOneLineBlock": true}], 17 | "curly": ["off"], 18 | "func-style": ["off"], 19 | "prefer-template": ["off"], 20 | "prefer-arrow-callback": ["off"], 21 | "no-use-before-define": ["off"], 22 | "max-statements-per-line": ["off"], 23 | "prefer-rest-params": ["off"], 24 | "class-methods-use-this": ["off"], 25 | "camelcase": ["off"], 26 | "indent": ["off"], 27 | "func-name-matching": ["off"], 28 | "no-console": 2, 29 | "eqeqeq": [2, "smart"], 30 | "no-eq-null": ["off"], 31 | "no-param-reassign": ["off"], 32 | "no-shadow": ["off"], 33 | "keyword-spacing": ["off"], 34 | "no-plusplus": ["off"], 35 | "operator-assignment": ["off"], 36 | "one-var": ["off"], 37 | "generator-star-spacing": ["error", {"before": false, "after": false}], 38 | "no-mixed-operators": ["off"], 39 | "capitalized-comments": ["off"], 40 | "init-declarations": ["off"], 41 | "function-call-argument-newline": ["off"], 42 | "no-multi-assign": ["off"], 43 | "no-invalid-this": ["off"] 44 | }, 45 | "overrides": [ 46 | { 47 | "files": ["test/**"], 48 | "env": {"node": true}, 49 | "globals": {"Promise": false}, 50 | "rules": { 51 | "arrow-body-style": ["off"], 52 | "no-sequences": ["off"], 53 | "max-len": ["off"], 54 | "space-before-function-paren": ["error", "always"] 55 | } 56 | } 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [Avaq] 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | reviewers: 9 | - Avaq 10 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - master 8 | - 11.x 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout Code 15 | uses: actions/checkout@v1 16 | - name: Install NodeJS 17 | uses: actions/setup-node@v1 18 | with: 19 | node-version: 18.x 20 | - name: Install Dependencies 21 | run: npm install 22 | - name: Execute Tests 23 | run: npm test 24 | - name: Upload Coverage Report 25 | run: npm run coverage:upload 26 | env: 27 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bench/node_modules/ 2 | /coverage/ 3 | /node_modules/ 4 | /perf/ 5 | 6 | /index.cjs 7 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | Please refer to the [release notes](https://github.com/fluture-js/Fluture/releases). 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution guide 2 | 3 | ## Making a contribution 4 | 5 | * Fork and clone the project 6 | * Commit changes to a branch named after the work that was done 7 | * Make sure the tests pass locally 8 | * Create a pull request 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2020 Aldwin Vlasblom 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /bench/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": ["warp/node", "warp/es6"], 4 | "rules": { 5 | "max-len": [0] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /bench/.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | save=false 3 | -------------------------------------------------------------------------------- /bench/README.md: -------------------------------------------------------------------------------- 1 | # Benchmarks 2 | 3 | ## Running 4 | 5 | * Install node modules required for the benchmark you wish to run 6 | * Use `npm run bench -- --help` for options 7 | 8 | For example, let's say you like to know how the performance of `parallel` 9 | compares to what it was at version 5.x.x: 10 | 11 | ```console 12 | $ (cd bench && npm install fluture@5.x.x) 13 | $ npm run bench -- --benchmark fluture --match *.parallel.* 14 | ``` 15 | -------------------------------------------------------------------------------- /bench/fluture.js: -------------------------------------------------------------------------------- 1 | /* global setImmediate */ 2 | 3 | import bench from 'sanctuary-benchmark'; 4 | import Old from 'fluture'; 5 | import * as New from '../index.js'; 6 | 7 | const config = {leftHeader: 'Old', rightHeader: 'New'}; 8 | 9 | const noop = () => {}; 10 | const compose = (f, g) => x => f(g(x)); 11 | const run = m => m.constructor.fork(noop)(noop)(m); 12 | const plus1 = x => x + 1; 13 | const arr = (T, length) => Array.from({length}, (_, i) => T.resolve(i)); 14 | const fast = (T, x) => T((rej, res) => void setImmediate(res, x)); 15 | const slow = (T, x) => T.after(1)(x); 16 | 17 | export default bench(Old, New, config, { 18 | 19 | 'def.interpreter.Future': [ 20 | {}, ({Future}) => Future((rej, res) => res(1)) 21 | ], 22 | 23 | 'def.interpreter.resolve': [ 24 | {}, ({resolve}) => resolve(1) 25 | ], 26 | 27 | 'def.interpreter.reject': [ 28 | {}, ({reject}) => reject(1) 29 | ], 30 | 31 | 'def.interpreter.after': [ 32 | {}, ({after}) => after(1)(1) 33 | ], 34 | 35 | 'def.interpreter.attempt': [ 36 | {}, ({attempt}) => attempt(noop) 37 | ], 38 | 39 | 'def.interpreter.cache': [ 40 | {}, ({resolve, cache}) => cache(resolve(1)) 41 | ], 42 | 43 | 'def.interpreter.encase': [ 44 | {}, ({encase}) => encase(noop)(1) 45 | ], 46 | 47 | 'def.interpreter.encaseP': [ 48 | {}, ({encaseP}) => encaseP(noop)(1) 49 | ], 50 | 51 | 'def.interpreter.go': [ 52 | {}, ({go}) => go(noop) 53 | ], 54 | 55 | 'def.interpreter.hook': [ 56 | {}, ({resolve, hook}) => hook(resolve(1))(noop)(noop) 57 | ], 58 | 59 | 'def.interpreter.node': [ 60 | {}, ({node}) => node(done => done(null, 1)) 61 | ], 62 | 63 | 'def.interpreter.parallel': [ 64 | {}, ({resolve, parallel}) => parallel(1)([resolve(1)]) 65 | ], 66 | 67 | 'def.transformation.map': [ 68 | {}, ({resolve, map}) => map(plus1)(resolve(1)) 69 | ], 70 | 71 | 'def.transformation.chain': [ 72 | {}, ({resolve, chain}) => chain(plus1)(resolve(1)) 73 | ], 74 | 75 | 'run.interpreter.Future': [ 76 | {}, ({Future}) => run(Future((rej, res) => res(1))) 77 | ], 78 | 79 | 'run.interpreter.parallel.empty': [ 80 | {}, ({parallel}) => run(parallel(1)([])) 81 | ], 82 | 83 | 'run.interpreter.parallel.small.sequential': [ 84 | {}, ({Future, parallel}) => run(parallel(1)(arr(Future, 2))) 85 | ], 86 | 87 | 'run.interpreter.parallel.small.concurrent': [ 88 | {}, ({Future, parallel}) => run(parallel(2)(arr(Future, 2))) 89 | ], 90 | 91 | 'run.interpreter.parallel.big.sequential': [ 92 | {}, ({Future, parallel}) => run(parallel(1)(arr(Future, 100))) 93 | ], 94 | 95 | 'run.interpreter.parallel.big.concurrent': [ 96 | {}, ({Future, parallel}) => run(parallel(2)(arr(Future, 100))) 97 | ], 98 | 99 | 'run.interpreter.go': [ 100 | {}, ({go, resolve}) => run(go(function*(){ 101 | return (yield resolve(1)) + (yield resolve(2)); 102 | })) 103 | ], 104 | 105 | 'run.transformation.sync.map': [ 106 | {}, ({resolve, map}) => run(map(plus1)(resolve(1))) 107 | ], 108 | 109 | 'run.transformation.sync.swap.one': [ 110 | {}, ({resolve, swap}) => run(swap(resolve(42))) 111 | ], 112 | 113 | 'run.transformation.sync.swap.many': [ 114 | {}, ({resolve, swap}) => { 115 | let m = resolve(1); 116 | for(let i = 0; i < 1000; i++){ m = swap(m) } 117 | run(m); 118 | } 119 | ], 120 | 121 | 'run.transformation.sync.chain.one': [ 122 | {}, ({resolve, chain}) => run(chain(compose(resolve, plus1))(resolve(1))) 123 | ], 124 | 125 | 'run.transformation.sync.chain.many': [ 126 | {}, ({chain, resolve}) => { 127 | const f = compose(resolve, plus1); 128 | let m = resolve(1); 129 | for(let i = 0; i < 1000; i++){ m = chain(f)(m) } 130 | run(m); 131 | } 132 | ], 133 | 134 | 'run.transformation.async.map': [ 135 | {defer: true}, ({Future, map, value}, [d]) => { 136 | map(plus1)(fast(Future, 1)).pipe(value(() => d.resolve())); 137 | } 138 | ], 139 | 140 | 'run.transformation.async.chain.one': [ 141 | {defer: true}, ({Future, chain, value}, [d]) => { 142 | chain(x => fast(Future, plus1(x)))(fast(Future, 1)) 143 | .pipe(value(() => d.resolve())); 144 | } 145 | ], 146 | 147 | 'run.transformation.async.chain.many': [ 148 | {defer: true}, ({Future, chain, value}, [d]) => { 149 | const f = x => fast(Future, plus1(x)); 150 | let m = fast(Future, 1); 151 | for(let i = 0; i < 100; i++){ m = chain(f)(m) } 152 | m.pipe(value(() => d.resolve())); 153 | } 154 | ], 155 | 156 | 'run.transformation.parallel.async.race.fast-vs-slow': [ 157 | {defer: true}, ({Future, race, value}, [d]) => { 158 | const a = fast(Future, 1); 159 | const b = slow(Future, 1); 160 | race(a)(b).pipe(value(() => d.resolve())); 161 | } 162 | ], 163 | 164 | 'run.transformation.parallel.async.race.slow-vs-fast': [ 165 | {defer: true}, ({Future, race, value}, [d]) => { 166 | const a = slow(Future, 1); 167 | const b = fast(Future, 1); 168 | race(a)(b).pipe(value(() => d.resolve())); 169 | } 170 | ], 171 | 172 | 'run.transformation.parallel.async.race.slow-vs-slow': [ 173 | {defer: true}, ({Future, race, value}, [d]) => { 174 | const a = slow(Future, 1); 175 | const b = slow(Future, 1); 176 | race(a)(b).pipe(value(() => d.resolve())); 177 | } 178 | ], 179 | 180 | }); 181 | -------------------------------------------------------------------------------- /bench/folktale.js: -------------------------------------------------------------------------------- 1 | import bench from 'sanctuary-benchmark'; 2 | import * as Future from '../index.js'; 3 | import folktale from 'folktale/concurrency/task/index.js'; 4 | 5 | const noop = () => {}; 6 | const plus1 = x => x + 1; 7 | const repeat = (n, f) => x => Array.from({length: n}).reduce(x => f(x), x); 8 | 9 | const map1000 = repeat(1000, Future.map(plus1)); 10 | const chain1000 = repeat(1000, Future.chain(plus1)); 11 | 12 | const createTask = x => folktale.task(resolver => resolver.resolve(x)); 13 | const createFuture = x => Future.Future((rej, res) => { res(x); return noop }); 14 | const consumeTask = m => m.run().listen({onCancelled: noop, onRejected: noop, onResolved: noop}); 15 | const consumeFuture = Future.fork(noop)(noop); 16 | 17 | const config = {leftHeader: 'Folktale', rightHeader: 'Fluture'}; 18 | 19 | const left = { 20 | create: createTask, 21 | consume: consumeTask, 22 | one: createTask(1), 23 | mapped: map1000(createTask(1)) 24 | }; 25 | 26 | const right = { 27 | create: createFuture, 28 | consume: consumeFuture, 29 | one: createFuture(1), 30 | mapped: map1000(createFuture(1)) 31 | }; 32 | 33 | export default bench(left, right, config, { 34 | 35 | 'create.construct': [ 36 | {}, ({create}) => repeat(1000, create)(1) 37 | ], 38 | 39 | 'create.map': [ 40 | {}, ({one}) => map1000(one) 41 | ], 42 | 43 | 'create.chain': [ 44 | {}, ({one}) => chain1000(one) 45 | ], 46 | 47 | 'consume.noop': [ 48 | {}, ({one, consume}) => consume(one) 49 | ], 50 | 51 | 'consume.map.1': [ 52 | {}, ({one, consume}) => consume(Future.map(plus1)(one)) 53 | ], 54 | 55 | 'consume.map.1000': [ 56 | {}, ({mapped, consume}) => consume(mapped) 57 | ], 58 | 59 | 'consume.chain': [ 60 | {}, ({create, consume, one}) => consume(Future.chain(x => create(x + 1))(one)) 61 | ], 62 | 63 | }); 64 | -------------------------------------------------------------------------------- /bench/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fluture-benchmarks", 3 | "type": "module", 4 | "version": "1.0.0", 5 | "description": "A package.json for the purpose of being able to install dependencies in here", 6 | "author": "Aldwin Vlasblom (https://github.com/Avaq)", 7 | "license": "MIT", 8 | "private": true 9 | } 10 | -------------------------------------------------------------------------------- /bench/promise.js: -------------------------------------------------------------------------------- 1 | import bench from 'sanctuary-benchmark'; 2 | import * as Future from '../index.js'; 3 | 4 | const plus1 = x => x + 1; 5 | const repeat = (n, f, x) => Array.from({length: n}).reduce(x => f(x), x); 6 | const Left = x => ({left: true, right: false, value: x}); 7 | const Right = x => ({left: false, right: true, value: x}); 8 | 9 | const config = {leftHeader: 'Promise', rightHeader: 'Fluture'}; 10 | 11 | const def = f => [ 12 | {defer: true}, 13 | (x, [d]) => f(x).then(() => d.resolve()), 14 | (x, [d]) => Future.value(() => d.resolve())(f(x)) 15 | ]; 16 | 17 | const PromiseInterop = { 18 | resolve: x => Promise.resolve(x), 19 | map: f => p => p.then(f), 20 | chain: f => p => p.then(f), 21 | coalesce: f => g => p => p.then(g, f), 22 | race: a => b => Promise.race([a, b]), 23 | after: n => x => new Promise(res => setTimeout(res, n, x)) 24 | }; 25 | 26 | export default bench(PromiseInterop, Future, config, { 27 | 28 | 'resolve': def( 29 | ({resolve}) => repeat(1000, resolve, 1) 30 | ), 31 | 32 | 'after': def( 33 | ({after}) => after(10)(1) 34 | ), 35 | 36 | 'map': def( 37 | ({map, resolve}) => repeat(1000, map(plus1), resolve(1)) 38 | ), 39 | 40 | 'coalesce': def( 41 | ({coalesce, resolve}) => repeat(1000, coalesce(Left)(Right), resolve(1)) 42 | ), 43 | 44 | 'chain.sync': def( 45 | ({chain, resolve}) => repeat(1000, chain(x => resolve(plus1(x))), resolve(1)) 46 | ), 47 | 48 | 'chain.async': def( 49 | ({chain, after}) => repeat(5, chain(x => after(1)(plus1(x))), after(1)(1)) 50 | ), 51 | 52 | 'race.sync': def( 53 | ({race, resolve}) => repeat(1000, race(resolve(2)), resolve(1)) 54 | ), 55 | 56 | 'race.async': def( 57 | ({race, after}) => repeat(5, race(after(1)(2)), after(1)(1)) 58 | ), 59 | 60 | }); 61 | -------------------------------------------------------------------------------- /index.cjs.js: -------------------------------------------------------------------------------- 1 | import * as Fluture from './index.js'; 2 | export default Object.assign(Fluture.Future, Fluture); 3 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | export { 2 | Future as default, 3 | Future, 4 | isFuture, 5 | isNever, 6 | never, 7 | reject, 8 | resolve, 9 | } from './src/future.js'; 10 | 11 | export {after} from './src/after.js'; 12 | export {alt} from './src/alt.js'; 13 | export {and} from './src/and.js'; 14 | export {ap} from './src/ap.js'; 15 | export {attemptP} from './src/attempt-p.js'; 16 | export {attempt} from './src/attempt.js'; 17 | export {bimap} from './src/bimap.js'; 18 | export {bichain} from './src/bichain.js'; 19 | export {both} from './src/both.js'; 20 | export {cache} from './src/cache.js'; 21 | export {chainRej} from './src/chain-rej.js'; 22 | export {chain} from './src/chain.js'; 23 | export {done} from './src/done.js'; 24 | export {encaseP} from './src/encase-p.js'; 25 | export {encase} from './src/encase.js'; 26 | export {extractLeft} from './src/extract-left.js'; 27 | export {extractRight} from './src/extract-right.js'; 28 | export {coalesce} from './src/coalesce.js'; 29 | export {forkCatch} from './src/fork-catch.js'; 30 | export {fork} from './src/fork.js'; 31 | export {go} from './src/go.js'; 32 | export {hook} from './src/hook.js'; 33 | export {lastly} from './src/lastly.js'; 34 | export {mapRej} from './src/map-rej.js'; 35 | export {map} from './src/map.js'; 36 | export {node} from './src/node.js'; 37 | export {pap} from './src/pap.js'; 38 | export {parallel} from './src/parallel.js'; 39 | export {Par} from './src/par.js'; 40 | export {promise} from './src/promise.js'; 41 | export {race} from './src/race.js'; 42 | export {rejectAfter} from './src/reject-after.js'; 43 | export {seq} from './src/seq.js'; 44 | export {swap} from './src/swap.js'; 45 | export {value} from './src/value.js'; 46 | 47 | export {debugMode} from './src/internal/debug.js'; 48 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluture-js/Fluture/1d925dea0008cd0324e757c179c12a7ea6b5aaca/logo.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fluture", 3 | "version": "14.0.0", 4 | "description": "FantasyLand compliant (monadic) alternative to Promises", 5 | "main": "index.cjs", 6 | "type": "module", 7 | "types": "index.d.ts", 8 | "module": "index.js", 9 | "exports": { 10 | ".": { 11 | "require": "./index.cjs", 12 | "import": "./index.js", 13 | "default": "./index.cjs" 14 | }, 15 | "./index.js": "./index.js", 16 | "./test/arbitraries.js": "./test/arbitraries.js", 17 | "./test/assertions.js": "./test/assertions.js" 18 | }, 19 | "files": [ 20 | "src", 21 | "index.cjs", 22 | "index.js", 23 | "index.d.ts", 24 | "test/arbitraries.js", 25 | "test/assertions.js" 26 | ], 27 | "repository": "https://github.com/fluture-js/Fluture.git", 28 | "scripts": { 29 | "bench": "node ./scripts/bench.js", 30 | "build": "rollup -c rollup.config.js", 31 | "build:dist": "rollup -c rollup.config.dist.js", 32 | "clean": "rimraf npm-debug.log coverage index.cjs .esm-cache .nyc_output node_modules/.cache", 33 | "lint": "eslint src test index.js index.cjs.js", 34 | "lint:readme": "remark --no-stdout --frail -u remark-validate-links README.md", 35 | "release": "xyz --edit --repo git@github.com:fluture-js/Fluture.git --tag 'X.Y.Z' --script scripts/distribute --increment", 36 | "test": "npm run lint && npm run lint:readme && ./scripts/test-esm && npm run test:code && npm run test:types && npm run test:build", 37 | "test:code": "c8 oletus test/unit/*.js test/integration/*.js test/prop/*.js", 38 | "test:mem": "NODE_OPTIONS='--experimental-modules --no-warnings' ./scripts/test-mem", 39 | "test:build": "npm run build && oletus test/build/*.js", 40 | "coverage:upload": "c8 report --reporter=text-lcov > coverage.lcov && codecov", 41 | "coverage:report": "c8 report --reporter=html", 42 | "test:types": "tsd" 43 | }, 44 | "author": "Aldwin Vlasblom (https://github.com/Avaq)", 45 | "homepage": "https://github.com/fluture-js/Fluture", 46 | "bugs": { 47 | "url": "https://github.com/fluture-js/Fluture/issues" 48 | }, 49 | "license": "MIT", 50 | "engines": { 51 | "node": ">=4.0.0" 52 | }, 53 | "keywords": [ 54 | "algebraic", 55 | "async", 56 | "asynchronous", 57 | "browser", 58 | "control-flow", 59 | "fantasy-land", 60 | "fp", 61 | "functional", 62 | "functor", 63 | "future", 64 | "library", 65 | "monad", 66 | "monadic", 67 | "node", 68 | "parallel", 69 | "promise", 70 | "sequential" 71 | ], 72 | "dependencies": { 73 | "sanctuary-show": "^2.0.0", 74 | "sanctuary-type-identifiers": "^4.0.0" 75 | }, 76 | "devDependencies": { 77 | "@rollup/plugin-commonjs": "^25.0.0", 78 | "@rollup/plugin-node-resolve": "^15.0.1", 79 | "c8": "^9.0.0", 80 | "chai": "^4.1.2", 81 | "codecov": "^3.6.1", 82 | "es-check": "^7.0.0", 83 | "eslint": "^8.54.0", 84 | "eslint-config-warp": "^7.1.0", 85 | "fantasy-laws": "^2.0.1", 86 | "jsverify": "^0.8.3", 87 | "oletus": "^4.0.0", 88 | "ramda": "^0.29.1", 89 | "remark-cli": "^12.0.0", 90 | "remark-validate-links": "^13.0.0", 91 | "rimraf": "^5.0.0", 92 | "rollup": "^4.5.2", 93 | "sanctuary-benchmark": "^1.0.0", 94 | "sanctuary-either": "^2.0.0", 95 | "sanctuary-type-classes": "^13.0.0", 96 | "tsd": "^0.30.0", 97 | "typescript": "^5.0.4", 98 | "xyz": "^4.0.0" 99 | }, 100 | "tsd": { 101 | "directory": "test/types" 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /rollup.config.dist.js: -------------------------------------------------------------------------------- 1 | /* global process Set */ 2 | 3 | import {readFileSync} from 'fs'; 4 | import node from '@rollup/plugin-node-resolve'; 5 | import commonjs from '@rollup/plugin-commonjs'; 6 | 7 | var pkg = JSON.parse(readFileSync('package.json', 'utf8')); 8 | 9 | var dependencies = pkg => { 10 | var deps = Object.keys(pkg.dependencies || {}).concat(Object.keys(pkg.peerDependencies || {})); 11 | return Array.from(new Set(deps.concat(deps.flatMap(dependency => ( 12 | dependencies(JSON.parse(readFileSync(`node_modules/${dependency}/package.json`, 'utf8'))) 13 | ))))); 14 | }; 15 | 16 | var banner = `/** 17 | * Fluture bundled; version ${process.env.VERSION || `${pkg.version} (dirty)`} 18 | */ 19 | `; 20 | 21 | var footer = `/** Fluture license 22 | 23 | ${readFileSync('./LICENSE')}*/ 24 | 25 | ${dependencies(pkg).map(dependency => `/** ${dependency} license 26 | 27 | ${readFileSync(`./node_modules/${dependency}/LICENSE`)}*/`).join('\n\n')}`; 28 | 29 | var typeref = `/// `; 32 | 33 | export default [{ 34 | input: 'index.cjs.js', 35 | plugins: [node(), commonjs({include: 'node_modules/**'})], 36 | output: { 37 | banner: banner, 38 | footer: footer, 39 | format: 'iife', 40 | name: 'Fluture', 41 | file: 'dist/bundle.js', 42 | }, 43 | }, { 44 | input: 'index.js', 45 | plugins: [node(), commonjs({include: 'node_modules/**'})], 46 | output: { 47 | banner: `${banner}\n${typeref}\n`, 48 | footer: footer, 49 | format: 'es', 50 | file: 'dist/module.js', 51 | }, 52 | }]; 53 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | var dependencies = { 2 | 'sanctuary-show': 'sanctuaryShow', 3 | 'sanctuary-type-identifiers': 'sanctuaryTypeIdentifiers', 4 | }; 5 | 6 | export default { 7 | input: 'index.cjs.js', 8 | external: Object.keys(dependencies), 9 | output: { 10 | format: 'umd', 11 | file: 'index.cjs', 12 | name: 'Fluture', 13 | globals: dependencies, 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /scripts/bench.js: -------------------------------------------------------------------------------- 1 | const suite = process.argv[2]; 2 | const options = process.argv[3] ? JSON.parse(process.argv[3]) : {}; 3 | 4 | import(`../bench/${suite}.js`).then(module => ( 5 | module.default(options) 6 | ), e => { 7 | console.error(e.stack); 8 | }); 9 | -------------------------------------------------------------------------------- /scripts/distribute: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eufo pipefail 3 | 4 | echo "Publishing as $(npm whoami)." 5 | 6 | if ! npm outdated --long; then 7 | read -rp "Continue? [y/N] " choice 8 | if [[ "${choice-n}" != 'y' ]]; then 9 | echo 'Package distribution aborted.' 10 | exit 2 11 | fi 12 | fi 13 | 14 | npm run build 15 | npm run build:dist 16 | sed --in-place "s/Fluture@$PREVIOUS_VERSION/Fluture@$VERSION/" README.md 17 | git add dist README.md 18 | -------------------------------------------------------------------------------- /scripts/test-esm: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eufo pipefail 3 | 4 | version=$(node --print 'process.versions.node.split(".")[0]') 5 | 6 | if [[ "$version" -lt 12 ]]; then 7 | echo 'Skipping esm test on Node versions below 12' 8 | elif node --experimental-modules --no-warnings index.js; then 9 | echo 'No problems with the EcmaScript module' 10 | else 11 | echo 'Problem encountered loading the EcmaScript module' 12 | exit 1 13 | fi 14 | -------------------------------------------------------------------------------- /scripts/test-mem: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* global process setImmediate */ 4 | 5 | import {Future, fork, race, chain, resolve as sync} from '../index.js'; 6 | import {log} from 'util'; 7 | 8 | var start = Date.now(); 9 | var batch = 0; 10 | var stamp = Date.now(); 11 | 12 | function report(){ 13 | var memMB = process.memoryUsage().rss / 1048576; 14 | var now = Date.now(); 15 | var passed = now - stamp; 16 | batch = batch + 1; 17 | if(passed >= 5000){ 18 | log( 19 | '-BATCH:', batch, 20 | '-OPS:', Math.round(batch / ((now - start) / passed) / (passed / 1000)), 21 | '-MEM:', memMB, 'MB' 22 | ); 23 | stamp = now; 24 | } 25 | } 26 | 27 | function noop(){} 28 | 29 | function async(x){ 30 | return Future(function(l, r){ setImmediate(r, x); return noop }); 31 | } 32 | 33 | var cases = Object.create(null); 34 | 35 | //Should infinitely run until finally running out of memory. 36 | cases.syncHeadRecursion = function recur(){ 37 | report(); 38 | return race(sync('r'))(chain(recur)(sync('l'))); 39 | }; 40 | 41 | //Should immediately exit with "l". 42 | cases.syncDeepRecursion = function recur(){ 43 | report(); 44 | return race(chain(recur)(sync('r')))(sync('l')); 45 | }; 46 | 47 | //Should infinitely run without any problems. 48 | cases.syncTailRecursion = function recur(){ 49 | report(); 50 | return chain(recur)(race(sync('r'))(sync('l'))); 51 | }; 52 | 53 | //Should immediately exit with "r". 54 | cases.asyncHeadRecursion = function recur(){ 55 | report(); 56 | return race(async('r'))(chain(recur)(async('l'))); 57 | }; 58 | 59 | //Should immediately exit with "l". 60 | cases.asyncDeepRecursion = function recur(){ 61 | report(); 62 | return race(chain(recur)(async('r')))(async('l')); 63 | }; 64 | 65 | //Should infinitely run without any problems. 66 | cases.asyncTailRecursion = function recur(){ 67 | report(); 68 | return chain(recur)(race(async('r'))(async('l'))); 69 | }; 70 | 71 | //Expected to run out of memory. 72 | cases.debugModeSyncTailRecursion = function(){ 73 | Future.debugMode(true); 74 | return cases.syncTailRecursion(); 75 | }; 76 | 77 | //Expected to run out of memory. 78 | cases.debugModeAsyncTailRecursion = function(){ 79 | Future.debugMode(true); 80 | return cases.asyncTailRecursion(); 81 | }; 82 | 83 | var f = cases[process.argv[2]]; 84 | 85 | if(typeof f !== 'function'){ 86 | console.log('Usage:\n\n test-mem \n\nPossible cases:\n'); 87 | Object.keys(cases).forEach(function(k){console.log(` ${k}`)}); 88 | process.exit(0); 89 | } 90 | 91 | log('PID', process.pid); 92 | 93 | var cancel = fork 94 | (function(e){console.error(e.stack); process.exit(1)}) 95 | (function(v){log('resolved', v)}) 96 | (f()); 97 | 98 | process.once('SIGINT', () => { 99 | log('SIGINT caught. Cancelling...'); 100 | cancel(); 101 | }); 102 | -------------------------------------------------------------------------------- /src/after.js: -------------------------------------------------------------------------------- 1 | import { 2 | any, 3 | application, 4 | application1, 5 | createInterpreter, 6 | never, 7 | positiveInteger, 8 | } from './future.js'; 9 | 10 | export var After = createInterpreter(2, 'after', function After$interpret(rec, rej, res){ 11 | var id = setTimeout(res, this.$1, this.$2); 12 | return function After$cancel(){ clearTimeout(id) }; 13 | }); 14 | 15 | After.prototype.extractRight = function After$extractRight(){ 16 | return [this.$2]; 17 | }; 18 | 19 | function alwaysNever(_){ 20 | return never; 21 | } 22 | 23 | export function after(time){ 24 | var context1 = application1(after, positiveInteger, arguments); 25 | return time === Infinity ? alwaysNever : (function after(value){ 26 | var context2 = application(2, after, any, arguments, context1); 27 | return new After(context2, time, value); 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /src/alt.js: -------------------------------------------------------------------------------- 1 | import {FL} from './internal/const.js'; 2 | import {invalidArgumentOf} from './internal/error.js'; 3 | import {isAlt} from './internal/predicates.js'; 4 | import { 5 | AltTransformation, 6 | application, 7 | application1, 8 | future, 9 | isFuture, 10 | } from './future.js'; 11 | 12 | export var alternative = {pred: isAlt, error: invalidArgumentOf('have Alt implemented')}; 13 | 14 | export function alt(left){ 15 | if(isFuture(left)){ 16 | var context1 = application1(alt, future, arguments); 17 | return function alt(right){ 18 | var context2 = application(2, alt, future, arguments, context1); 19 | return right._transform(new AltTransformation(context2, left)); 20 | }; 21 | } 22 | 23 | var context = application1(alt, alternative, arguments); 24 | return function alt(right){ 25 | application(2, alt, alternative, arguments, context); 26 | return left[FL.alt](right); 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /src/and.js: -------------------------------------------------------------------------------- 1 | import {createTransformation, application1, application, future} from './future.js'; 2 | 3 | export var AndTransformation = createTransformation(1, 'and', { 4 | resolved: function AndTransformation$resolved(){ return this.$1 }, 5 | }); 6 | 7 | export function and(left){ 8 | var context1 = application1(and, future, arguments); 9 | return function and(right){ 10 | var context2 = application(2, and, future, arguments, context1); 11 | return right._transform(new AndTransformation(context2, left)); 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /src/ap.js: -------------------------------------------------------------------------------- 1 | import {FL} from './internal/const.js'; 2 | import {invalidArgumentOf} from './internal/error.js'; 3 | import {isApply} from './internal/predicates.js'; 4 | import {isFuture, ApTransformation, application1, application, future} from './future.js'; 5 | 6 | export var apply = {pred: isApply, error: invalidArgumentOf('have Apply implemented')}; 7 | 8 | export function ap(mx){ 9 | if(isFuture(mx)){ 10 | var context1 = application1(ap, future, arguments); 11 | return function ap(mf){ 12 | var context2 = application(2, ap, future, arguments, context1); 13 | return mf._transform(new ApTransformation(context2, mx)); 14 | }; 15 | } 16 | 17 | var context = application1(ap, apply, arguments); 18 | return function ap(mf){ 19 | application(2, ap, apply, arguments, context); 20 | return mx[FL.ap](mf); 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /src/attempt-p.js: -------------------------------------------------------------------------------- 1 | import {encaseP} from './encase-p.js'; 2 | 3 | export function attemptP(_){ 4 | return encaseP.apply(this, arguments)(undefined); 5 | } 6 | -------------------------------------------------------------------------------- /src/attempt.js: -------------------------------------------------------------------------------- 1 | import {encase} from './encase.js'; 2 | 3 | export function attempt(_){ 4 | return encase.apply(this, arguments)(undefined); 5 | } 6 | -------------------------------------------------------------------------------- /src/bichain.js: -------------------------------------------------------------------------------- 1 | import {createTransformation, future, application1, application, func} from './future.js'; 2 | import {call} from './internal/utils.js'; 3 | 4 | export var BichainTransformation = createTransformation(2, 'bichain', { 5 | rejected: function BichainTransformation$rejected(x){ return call(this.$1, x) }, 6 | resolved: function BichainTransformation$resolved(x){ return call(this.$2, x) }, 7 | }); 8 | 9 | export function bichain(f){ 10 | var context1 = application1(bichain, func, arguments); 11 | return function bichain(g){ 12 | var context2 = application(2, bichain, func, arguments, context1); 13 | return function bichain(m){ 14 | var context3 = application(3, bichain, future, arguments, context2); 15 | return m._transform(new BichainTransformation(context3, f, g)); 16 | }; 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /src/bimap.js: -------------------------------------------------------------------------------- 1 | import {FL} from './internal/const.js'; 2 | import {invalidArgumentOf} from './internal/error.js'; 3 | import {isBifunctor} from './internal/predicates.js'; 4 | import {isFuture, BimapTransformation, application1, application, func} from './future.js'; 5 | 6 | export var bifunctor = {pred: isBifunctor, error: invalidArgumentOf('have Bifunctor implemented')}; 7 | 8 | export function bimap(f){ 9 | var context1 = application1(bimap, func, arguments); 10 | return function bimap(g){ 11 | var context2 = application(2, bimap, func, arguments, context1); 12 | return function bimap(m){ 13 | var context3 = application(3, bimap, bifunctor, arguments, context2); 14 | return isFuture(m) ? 15 | m._transform(new BimapTransformation(context3, f, g)) : 16 | m[FL.bimap](f, g); 17 | }; 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /src/both.js: -------------------------------------------------------------------------------- 1 | import {createParallelTransformation, earlyCrash, earlyReject} from './internal/parallel.js'; 2 | import {noop} from './internal/utils.js'; 3 | import {createTransformation, Resolve, application1, application, future} from './future.js'; 4 | 5 | export var PairTransformation = createTransformation(1, 'pair', { 6 | resolved: function PairTransformation$resolved(x){ 7 | return new Resolve(this.context, [x, this.$1]); 8 | }, 9 | }); 10 | 11 | export var BothTransformation = 12 | createParallelTransformation('both', earlyCrash, earlyReject, noop, { 13 | resolved: function BothTransformation$resolved(x){ 14 | return this.$1._transform(new PairTransformation(this.context, x)); 15 | }, 16 | }); 17 | 18 | export function both(left){ 19 | var context1 = application1(both, future, arguments); 20 | return function both(right){ 21 | var context2 = application(2, both, future, arguments, context1); 22 | return right._transform(new BothTransformation(context2, left)); 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /src/cache.js: -------------------------------------------------------------------------------- 1 | import {noop} from './internal/utils.js'; 2 | import {createInterpreter, application1, future} from './future.js'; 3 | 4 | export var Cold = 0; 5 | export var Pending = 1; 6 | export var Crashed = 2; 7 | export var Rejected = 3; 8 | export var Resolved = 4; 9 | 10 | export function Queued(rec, rej, res){ 11 | this[Crashed] = rec; 12 | this[Rejected] = rej; 13 | this[Resolved] = res; 14 | } 15 | 16 | export var Cache = createInterpreter(1, 'cache', function Cache$interpret(rec, rej, res){ 17 | var cancel = noop; 18 | 19 | switch(this._state){ 20 | /* c8 ignore next 4 */ 21 | case Pending: cancel = this._addToQueue(rec, rej, res); break; 22 | case Crashed: rec(this._value); break; 23 | case Rejected: rej(this._value); break; 24 | case Resolved: res(this._value); break; 25 | default: 26 | this._queue = []; 27 | cancel = this._addToQueue(rec, rej, res); 28 | this.run(); 29 | } 30 | 31 | return cancel; 32 | }); 33 | 34 | Cache.prototype._cancel = noop; 35 | Cache.prototype._queue = null; 36 | Cache.prototype._queued = 0; 37 | Cache.prototype._value = undefined; 38 | Cache.prototype._state = Cold; 39 | 40 | Cache.prototype.extractLeft = function Cache$extractLeft(){ 41 | return this._state === Rejected ? [this._value] : []; 42 | }; 43 | 44 | Cache.prototype.extractRight = function Cache$extractRight(){ 45 | return this._state === Resolved ? [this._value] : []; 46 | }; 47 | 48 | Cache.prototype._addToQueue = function Cache$addToQueue(rec, rej, res){ 49 | var _this = this; 50 | if(_this._state > Pending) return noop; 51 | var i = _this._queue.push(new Queued(rec, rej, res)) - 1; 52 | _this._queued = _this._queued + 1; 53 | 54 | return function Cache$removeFromQueue(){ 55 | if(_this._state > Pending) return; 56 | _this._queue[i] = undefined; 57 | _this._queued = _this._queued - 1; 58 | if(_this._queued === 0) _this.reset(); 59 | }; 60 | }; 61 | 62 | Cache.prototype._drainQueue = function Cache$drainQueue(){ 63 | if(this._state <= Pending) return; 64 | if(this._queued === 0) return; 65 | var queue = this._queue; 66 | var length = queue.length; 67 | var state = this._state; 68 | var value = this._value; 69 | 70 | for(var i = 0; i < length; i++){ 71 | queue[i] && queue[i][state](value); 72 | queue[i] = undefined; 73 | } 74 | 75 | this._queue = undefined; 76 | this._queued = 0; 77 | }; 78 | 79 | Cache.prototype.crash = function Cache$crash(error){ 80 | if(this._state > Pending) return; 81 | this._value = error; 82 | this._state = Crashed; 83 | this._drainQueue(); 84 | }; 85 | 86 | Cache.prototype.reject = function Cache$reject(reason){ 87 | if(this._state > Pending) return; 88 | this._value = reason; 89 | this._state = Rejected; 90 | this._drainQueue(); 91 | }; 92 | 93 | Cache.prototype.resolve = function Cache$resolve(value){ 94 | if(this._state > Pending) return; 95 | this._value = value; 96 | this._state = Resolved; 97 | this._drainQueue(); 98 | }; 99 | 100 | Cache.prototype.run = function Cache$run(){ 101 | var _this = this; 102 | if(_this._state > Cold) return; 103 | _this._state = Pending; 104 | _this._cancel = _this.$1._interpret( 105 | function Cache$fork$rec(x){ _this.crash(x) }, 106 | function Cache$fork$rej(x){ _this.reject(x) }, 107 | function Cache$fork$res(x){ _this.resolve(x) } 108 | ); 109 | }; 110 | 111 | Cache.prototype.reset = function Cache$reset(){ 112 | if(this._state === Cold) return; 113 | if(this._state === Pending) this._cancel(); 114 | this._cancel = noop; 115 | this._queue = []; 116 | this._queued = 0; 117 | this._value = undefined; 118 | this._state = Cold; 119 | }; 120 | 121 | export function cache(m){ 122 | return new Cache(application1(cache, future, arguments), m); 123 | } 124 | -------------------------------------------------------------------------------- /src/chain-rej.js: -------------------------------------------------------------------------------- 1 | import {call} from './internal/utils.js'; 2 | import {createTransformation, application1, application, future, func} from './future.js'; 3 | 4 | export var ChainRejTransformation = createTransformation(1, 'chainRej', { 5 | rejected: function ChainRejTransformation$rejected(x){ return call(this.$1, x) }, 6 | }); 7 | 8 | export function chainRej(f){ 9 | var context1 = application1(chainRej, func, arguments); 10 | return function chainRej(m){ 11 | var context2 = application(2, chainRej, future, arguments, context1); 12 | return m._transform(new ChainRejTransformation(context2, f)); 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /src/chain.js: -------------------------------------------------------------------------------- 1 | import {FL} from './internal/const.js'; 2 | import {invalidArgumentOf} from './internal/error.js'; 3 | import {isChain} from './internal/predicates.js'; 4 | import {isFuture, application1, application, func, ChainTransformation} from './future.js'; 5 | 6 | export var monad = {pred: isChain, error: invalidArgumentOf('have Chain implemented')}; 7 | 8 | export function chain(f){ 9 | var context1 = application1(chain, func, arguments); 10 | return function chain(m){ 11 | var context2 = application(2, chain, monad, arguments, context1); 12 | return isFuture(m) ? 13 | m._transform(new ChainTransformation(context2, f)) : 14 | m[FL.chain](f); 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /src/coalesce.js: -------------------------------------------------------------------------------- 1 | import {call} from './internal/utils.js'; 2 | import {createTransformation, Resolve, application1, application, func, future} from './future.js'; 3 | 4 | export var CoalesceTransformation = createTransformation(2, 'coalesce', { 5 | rejected: function CoalesceTransformation$rejected(x){ 6 | return new Resolve(this.context, call(this.$1, x)); 7 | }, 8 | resolved: function CoalesceTransformation$resolved(x){ 9 | return new Resolve(this.context, call(this.$2, x)); 10 | }, 11 | }); 12 | 13 | export function coalesce(f){ 14 | var context1 = application1(coalesce, func, arguments); 15 | return function coalesce(g){ 16 | var context2 = application(2, coalesce, func, arguments, context1); 17 | return function coalesce(m){ 18 | var context3 = application(3, coalesce, future, arguments, context2); 19 | return m._transform(new CoalesceTransformation(context3, f, g)); 20 | }; 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /src/done.js: -------------------------------------------------------------------------------- 1 | import {application1, application, func, future} from './future.js'; 2 | import {raise} from './internal/utils.js'; 3 | 4 | export function done(callback){ 5 | var context1 = application1(done, func, arguments); 6 | function done$res(x){ 7 | callback(null, x); 8 | } 9 | return function done(m){ 10 | application(2, done, future, arguments, context1); 11 | return m._interpret(raise, callback, done$res); 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /src/encase-p.js: -------------------------------------------------------------------------------- 1 | import {wrapException, typeError} from './internal/error.js'; 2 | import {isThenable} from './internal/predicates.js'; 3 | import {noop, show} from './internal/utils.js'; 4 | import {createInterpreter, application1, application, func, any} from './future.js'; 5 | 6 | function invalidPromise(p, f, a){ 7 | return typeError( 8 | 'encaseP() expects the function it\'s given to return a Promise/Thenable' 9 | + '\n Actual: ' + show(p) + '\n From calling: ' + show(f) 10 | + '\n With: ' + show(a) 11 | ); 12 | } 13 | 14 | export var EncaseP = createInterpreter(2, 'encaseP', function EncaseP$interpret(rec, rej, res){ 15 | var open = true, fn = this.$1, arg = this.$2, p; 16 | try{ 17 | p = fn(arg); 18 | }catch(e){ 19 | rec(wrapException(e, this)); 20 | return noop; 21 | } 22 | if(!isThenable(p)){ 23 | rec(wrapException(invalidPromise(p, fn, arg), this)); 24 | return noop; 25 | } 26 | p.then(function EncaseP$res(x){ 27 | if(open){ 28 | open = false; 29 | res(x); 30 | } 31 | }, function EncaseP$rej(x){ 32 | if(open){ 33 | open = false; 34 | rej(x); 35 | } 36 | }); 37 | return function EncaseP$cancel(){ open = false }; 38 | }); 39 | 40 | export function encaseP(f){ 41 | var context1 = application1(encaseP, func, arguments); 42 | return function encaseP(x){ 43 | var context2 = application(2, encaseP, any, arguments, context1); 44 | return new EncaseP(context2, f, x); 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /src/encase.js: -------------------------------------------------------------------------------- 1 | import {noop} from './internal/utils.js'; 2 | import {createInterpreter, application1, application, func, any} from './future.js'; 3 | 4 | export var Encase = createInterpreter(2, 'encase', function Encase$interpret(rec, rej, res){ 5 | var fn = this.$1, r; 6 | try{ r = fn(this.$2) }catch(e){ rej(e); return noop } 7 | res(r); 8 | return noop; 9 | }); 10 | 11 | export function encase(f){ 12 | var context1 = application1(encase, func, arguments); 13 | return function encase(x){ 14 | var context2 = application(2, encase, any, arguments, context1); 15 | return new Encase(context2, f, x); 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /src/extract-left.js: -------------------------------------------------------------------------------- 1 | import {application1, future} from './future.js'; 2 | 3 | export function extractLeft(m){ 4 | application1(extractLeft, future, arguments); 5 | return m.extractLeft(); 6 | } 7 | -------------------------------------------------------------------------------- /src/extract-right.js: -------------------------------------------------------------------------------- 1 | import {application1, future} from './future.js'; 2 | 3 | export function extractRight(m){ 4 | application1(extractRight, future, arguments); 5 | return m.extractRight(); 6 | } 7 | -------------------------------------------------------------------------------- /src/fork-catch.js: -------------------------------------------------------------------------------- 1 | import {application, application1, func, future} from './future.js'; 2 | 3 | export function forkCatch(f){ 4 | var context1 = application1(forkCatch, func, arguments); 5 | return function forkCatch(g){ 6 | var context2 = application(2, forkCatch, func, arguments, context1); 7 | return function forkCatch(h){ 8 | var context3 = application(3, forkCatch, func, arguments, context2); 9 | return function forkCatch(m){ 10 | application(4, forkCatch, future, arguments, context3); 11 | return m._interpret(f, g, h); 12 | }; 13 | }; 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /src/fork.js: -------------------------------------------------------------------------------- 1 | import {raise} from './internal/utils.js'; 2 | import {application, application1, func, future} from './future.js'; 3 | 4 | export function fork(f){ 5 | var context1 = application1(fork, func, arguments); 6 | return function fork(g){ 7 | var context2 = application(2, fork, func, arguments, context1); 8 | return function fork(m){ 9 | application(3, fork, future, arguments, context2); 10 | return m._interpret(raise, f, g); 11 | }; 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /src/go.js: -------------------------------------------------------------------------------- 1 | /* eslint consistent-return: 0 */ 2 | 3 | import {typeError, invalidFuture, invalidArgument, wrapException} from './internal/error.js'; 4 | import {isIteration} from './internal/iteration.js'; 5 | import {isIterator} from './internal/predicates.js'; 6 | import {Undetermined, Synchronous, Asynchronous} from './internal/timing.js'; 7 | import {show, noop} from './internal/utils.js'; 8 | import {createInterpreter, isFuture, application1, func} from './future.js'; 9 | 10 | export function invalidIteration(o){ 11 | return typeError( 12 | 'The iterator did not return a valid iteration from iterator.next()\n' + 13 | ' Actual: ' + show(o) 14 | ); 15 | } 16 | 17 | export function invalidState(x){ 18 | return invalidFuture( 19 | 'go() expects the value produced by the iterator', x, 20 | '\n Tip: If you\'re using a generator, make sure you always yield a Future' 21 | ); 22 | } 23 | 24 | export var Go = createInterpreter(1, 'go', function Go$interpret(rec, rej, res){ 25 | var _this = this, timing = Undetermined, cancel = noop, state, value, iterator; 26 | 27 | function crash(e){ 28 | rec(wrapException(e, _this)); 29 | } 30 | 31 | try{ 32 | iterator = _this.$1(); 33 | }catch(e){ 34 | crash(e); 35 | return noop; 36 | } 37 | 38 | if(!isIterator(iterator)){ 39 | crash(invalidArgument('go', 0, 'return an iterator, maybe you forgot the "*"', iterator)); 40 | return noop; 41 | } 42 | 43 | function resolved(x){ 44 | value = x; 45 | if(timing === Asynchronous) return drain(); 46 | timing = Synchronous; 47 | } 48 | 49 | function drain(){ 50 | // eslint-disable-next-line no-constant-condition 51 | while(true){ 52 | try{ 53 | state = iterator.next(value); 54 | }catch(e){ 55 | return crash(e); 56 | } 57 | if(!isIteration(state)) return crash(invalidIteration(state)); 58 | if(state.done) break; 59 | if(!isFuture(state.value)) return crash(invalidState(state.value)); 60 | timing = Undetermined; 61 | cancel = state.value._interpret(crash, rej, resolved); 62 | if(timing === Undetermined) return (timing = Asynchronous); 63 | } 64 | res(state.value); 65 | } 66 | 67 | drain(); 68 | 69 | return function Go$cancel(){ cancel() }; 70 | }); 71 | 72 | export function go(generator){ 73 | return new Go(application1(go, func, arguments), generator); 74 | } 75 | -------------------------------------------------------------------------------- /src/hook.js: -------------------------------------------------------------------------------- 1 | import {noop, show, raise} from './internal/utils.js'; 2 | import {invalidFuture, wrapException} from './internal/error.js'; 3 | import {createInterpreter, isFuture, application1, application, func, future} from './future.js'; 4 | 5 | function invalidDisposal(m, f, x){ 6 | return invalidFuture( 7 | 'hook() expects the return value from the first function it\'s given', m, 8 | '\n From calling: ' + show(f) + '\n With: ' + show(x) 9 | ); 10 | } 11 | 12 | function invalidConsumption(m, f, x){ 13 | return invalidFuture( 14 | 'hook() expects the return value from the second function it\'s given', m, 15 | '\n From calling: ' + show(f) + '\n With: ' + show(x) 16 | ); 17 | } 18 | 19 | export var Hook = createInterpreter(3, 'hook', function Hook$interpret(rec, rej, res){ 20 | var _this = this, _acquire = this.$1, _dispose = this.$2, _consume = this.$3; 21 | var cancel, cancelConsume = noop, resource, value, cont = noop; 22 | 23 | function Hook$done(){ 24 | cont(value); 25 | } 26 | 27 | function Hook$rec(x){ 28 | rec(wrapException(x, _this)); 29 | } 30 | 31 | function Hook$dispose(){ 32 | var disposal; 33 | try{ 34 | disposal = _dispose(resource); 35 | }catch(e){ 36 | return Hook$rec(e); 37 | } 38 | if(!isFuture(disposal)){ 39 | return Hook$rec(invalidDisposal(disposal, _dispose, resource)); 40 | } 41 | cancel = Hook$cancelDisposal; 42 | disposal._interpret(Hook$rec, Hook$disposalRejected, Hook$done); 43 | } 44 | 45 | function Hook$cancelConsumption(){ 46 | cancelConsume(); 47 | Hook$dispose(); 48 | Hook$cancelDisposal(); 49 | } 50 | 51 | function Hook$cancelDisposal(){ 52 | cont = noop; 53 | } 54 | 55 | function Hook$disposalRejected(x){ 56 | Hook$rec(new Error('The disposal Future rejected with ' + show(x))); 57 | } 58 | 59 | function Hook$consumptionException(x){ 60 | cont = Hook$rec; 61 | value = x; 62 | Hook$dispose(); 63 | } 64 | 65 | function Hook$consumptionRejected(x){ 66 | cont = rej; 67 | value = x; 68 | Hook$dispose(); 69 | } 70 | 71 | function Hook$consumptionResolved(x){ 72 | cont = res; 73 | value = x; 74 | Hook$dispose(); 75 | } 76 | 77 | function Hook$consume(x){ 78 | resource = x; 79 | var consumption; 80 | try{ 81 | consumption = _consume(resource); 82 | }catch(e){ 83 | return Hook$consumptionException(e); 84 | } 85 | if(!isFuture(consumption)){ 86 | return Hook$consumptionException(invalidConsumption(consumption, _consume, resource)); 87 | } 88 | cancel = Hook$cancelConsumption; 89 | cancelConsume = consumption._interpret( 90 | Hook$consumptionException, 91 | Hook$consumptionRejected, 92 | Hook$consumptionResolved 93 | ); 94 | } 95 | 96 | var cancelAcquire = _acquire._interpret(Hook$rec, rej, Hook$consume); 97 | cancel = cancel || cancelAcquire; 98 | 99 | return function Hook$fork$cancel(){ 100 | rec = raise; 101 | cancel(); 102 | }; 103 | }); 104 | 105 | export function hook(acquire){ 106 | var context1 = application1(hook, future, arguments); 107 | return function hook(dispose){ 108 | var context2 = application(2, hook, func, arguments, context1); 109 | return function hook(consume){ 110 | var context3 = application(3, hook, func, arguments, context2); 111 | return new Hook(context3, acquire, dispose, consume); 112 | }; 113 | }; 114 | } 115 | -------------------------------------------------------------------------------- /src/internal/const.js: -------------------------------------------------------------------------------- 1 | export var FL = { 2 | alt: 'fantasy-land/alt', 3 | ap: 'fantasy-land/ap', 4 | bimap: 'fantasy-land/bimap', 5 | chain: 'fantasy-land/chain', 6 | chainRec: 'fantasy-land/chainRec', 7 | map: 'fantasy-land/map', 8 | of: 'fantasy-land/of', 9 | zero: 'fantasy-land/zero', 10 | }; 11 | 12 | export var ordinal = ['first', 'second', 'third', 'fourth', 'fifth']; 13 | 14 | export var namespace = 'fluture'; 15 | export var name = 'Future'; 16 | export var version = 5; 17 | 18 | export var $$type = namespace + '/' + name + '@' + version; 19 | -------------------------------------------------------------------------------- /src/internal/debug.js: -------------------------------------------------------------------------------- 1 | import {ordinal} from './const.js'; 2 | import {cons} from './list.js'; 3 | 4 | /* c8 ignore next */ 5 | var captureStackTrace = Error.captureStackTrace || captureStackTraceFallback; 6 | var _debug = debugHandleNone; 7 | 8 | export {captureStackTrace}; 9 | 10 | export function debugMode(debug){ 11 | _debug = debug ? debugHandleAll : debugHandleNone; 12 | } 13 | 14 | export function debugHandleNone(x){ 15 | return x; 16 | } 17 | 18 | export function debugHandleAll(x, fn, a, b, c){ 19 | return fn(a, b, c); 20 | } 21 | 22 | export function debug(x, fn, a, b, c){ 23 | return _debug(x, fn, a, b, c); 24 | } 25 | 26 | export function captureContext(previous, tag, fn){ 27 | return debug(previous, debugCaptureContext, previous, tag, fn); 28 | } 29 | 30 | export function debugCaptureContext(previous, tag, fn){ 31 | var context = {tag: tag, name: ' from ' + tag + ':'}; 32 | captureStackTrace(context, fn); 33 | return cons(context, previous); 34 | } 35 | 36 | export function captureApplicationContext(context, n, f){ 37 | return debug(context, debugCaptureApplicationContext, context, n, f); 38 | } 39 | 40 | export function debugCaptureApplicationContext(context, n, f){ 41 | return debugCaptureContext(context, ordinal[n - 1] + ' application of ' + f.name, f); 42 | } 43 | 44 | export function captureStackTraceFallback(x){ 45 | var e = new Error; 46 | if(typeof e.stack === 'string'){ 47 | x.stack = x.name + '\n' + e.stack.split('\n').slice(1).join('\n'); 48 | 49 | /* c8 ignore next 3 */ 50 | }else{ 51 | x.stack = x.name; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/internal/error.js: -------------------------------------------------------------------------------- 1 | import {show} from './utils.js'; 2 | import {ordinal, namespace, name, version} from './const.js'; 3 | import type from 'sanctuary-type-identifiers'; 4 | import {nil, cat} from './list.js'; 5 | import {captureStackTrace} from './debug.js'; 6 | 7 | function showArg(x){ 8 | return show(x) + ' :: ' + type.parse(type(x)).name; 9 | } 10 | 11 | export function error(message){ 12 | return new Error(message); 13 | } 14 | 15 | export function typeError(message){ 16 | return new TypeError(message); 17 | } 18 | 19 | export function invalidArgument(it, at, expected, actual){ 20 | return typeError( 21 | it + '() expects its ' + ordinal[at] + ' argument to ' + expected + '.' + 22 | '\n Actual: ' + showArg(actual) 23 | ); 24 | } 25 | 26 | export function invalidArgumentOf(expected){ 27 | return function(it, at, actual){ 28 | return invalidArgument(it, at, expected, actual); 29 | }; 30 | } 31 | 32 | export function invalidContext(it, actual){ 33 | return typeError( 34 | it + '() was invoked outside the context of a Future. You might want to use' 35 | + ' a dispatcher instead\n Called on: ' + show(actual) 36 | ); 37 | } 38 | 39 | export function invalidArity(f, args){ 40 | return new TypeError( 41 | f.name + '() expects to be called with a single argument per invocation\n' + 42 | ' Saw: ' + args.length + ' arguments' + 43 | Array.prototype.slice.call(args).map(function(arg, i){ 44 | return '\n ' + ( 45 | ordinal[i] ? 46 | ordinal[i].charAt(0).toUpperCase() + ordinal[i].slice(1) : 47 | 'Argument ' + String(i + 1) 48 | ) + ': ' + showArg(arg); 49 | }).join('') 50 | ); 51 | } 52 | 53 | function invalidNamespace(m, x){ 54 | return ( 55 | 'The Future was not created by ' + namespace + '. ' 56 | + 'Make sure you transform other Futures to ' + namespace + ' Futures. ' 57 | + 'Got ' + (x ? ('a Future from ' + x) : 'an unscoped Future') + '.' 58 | + '\n See: https://github.com/fluture-js/Fluture#casting-futures' 59 | ); 60 | } 61 | 62 | function invalidVersion(m, x){ 63 | return ( 64 | 'The Future was created by ' + (x < version ? 'an older' : 'a newer') 65 | + ' version of ' + namespace + '. ' 66 | + 'This means that one of the sources which creates Futures is outdated. ' 67 | + 'Update this source, or transform its created Futures to be compatible.' 68 | + '\n See: https://github.com/fluture-js/Fluture#casting-futures' 69 | ); 70 | } 71 | 72 | export function invalidFuture(desc, m, s){ 73 | var id = type.parse(type(m)); 74 | var info = id.name === name ? '\n' + ( 75 | id.namespace !== namespace ? invalidNamespace(m, id.namespace) 76 | : id.version !== version ? invalidVersion(m, id.version) 77 | : 'Nothing seems wrong. Contact the Fluture maintainers.') : ''; 78 | return typeError( 79 | desc + ' to be a valid Future.' + info + '\n' + 80 | ' Actual: ' + show(m) + ' :: ' + id.name + (s || '') 81 | ); 82 | } 83 | 84 | export function invalidFutureArgument(it, at, m, s){ 85 | return invalidFuture(it + '() expects its ' + ordinal[at] + ' argument', m, s); 86 | } 87 | 88 | export function ensureError(value, fn){ 89 | var message; 90 | try{ 91 | if(value instanceof Error) return value; 92 | message = 'A Non-Error was thrown from a Future: ' + show(value); 93 | }catch (_){ 94 | message = 'Something was thrown from a Future, but it could not be converted to String'; 95 | } 96 | var e = error(message); 97 | captureStackTrace(e, fn); 98 | return e; 99 | } 100 | 101 | export function assignUnenumerable(o, prop, value){ 102 | Object.defineProperty(o, prop, {value: value, writable: true, configurable: true}); 103 | } 104 | 105 | export function wrapException(caught, callingFuture){ 106 | var origin = ensureError(caught, wrapException); 107 | var context = cat(origin.context || nil, callingFuture.context); 108 | var e = error(origin.message); 109 | assignUnenumerable(e, 'future', origin.future || callingFuture); 110 | assignUnenumerable(e, 'reason', origin.reason || origin); 111 | assignUnenumerable(e, 'stack', e.reason.stack); 112 | return withExtraContext(e, context); 113 | } 114 | 115 | export function withExtraContext(e, context){ 116 | assignUnenumerable(e, 'context', context); 117 | assignUnenumerable(e, 'stack', e.stack + contextToStackTrace(context)); 118 | return e; 119 | } 120 | 121 | export function contextToStackTrace(context){ 122 | var stack = '', tail = context; 123 | while(tail !== nil){ 124 | stack = stack + '\n' + tail.head.stack; 125 | tail = tail.tail; 126 | } 127 | return stack; 128 | } 129 | -------------------------------------------------------------------------------- /src/internal/iteration.js: -------------------------------------------------------------------------------- 1 | import {isObject, isBoolean} from './predicates.js'; 2 | 3 | export function Next(x){ 4 | return {done: false, value: x}; 5 | } 6 | 7 | export function Done(x){ 8 | return {done: true, value: x}; 9 | } 10 | 11 | export function isIteration(x){ 12 | return isObject(x) && isBoolean(x.done); 13 | } 14 | -------------------------------------------------------------------------------- /src/internal/list.js: -------------------------------------------------------------------------------- 1 | export function List(head, tail){ 2 | this.head = head; 3 | this.tail = tail; 4 | } 5 | 6 | List.prototype.toJSON = function(){ 7 | return toArray(this); 8 | }; 9 | 10 | export var nil = new List(null, null); 11 | nil.tail = nil; 12 | 13 | export function isNil(list){ 14 | return list.tail === list; 15 | } 16 | 17 | // cons :: (a, List a) -> List a 18 | // -- O(1) append operation 19 | export function cons(head, tail){ 20 | return new List(head, tail); 21 | } 22 | 23 | // reverse :: List a -> List a 24 | // -- O(n) list reversal 25 | export function reverse(xs){ 26 | var ys = nil, tail = xs; 27 | while(!isNil(tail)){ 28 | ys = cons(tail.head, ys); 29 | tail = tail.tail; 30 | } 31 | return ys; 32 | } 33 | 34 | // cat :: (List a, List a) -> List a 35 | // -- O(n) list concatenation 36 | export function cat(xs, ys){ 37 | var zs = ys, tail = reverse(xs); 38 | while(!isNil(tail)){ 39 | zs = cons(tail.head, zs); 40 | tail = tail.tail; 41 | } 42 | return zs; 43 | } 44 | 45 | // toArray :: List a -> Array a 46 | // -- O(n) list to Array 47 | export function toArray(xs){ 48 | var tail = xs, arr = []; 49 | while(!isNil(tail)){ 50 | arr.push(tail.head); 51 | tail = tail.tail; 52 | } 53 | return arr; 54 | } 55 | -------------------------------------------------------------------------------- /src/internal/parallel.js: -------------------------------------------------------------------------------- 1 | import {noop} from './utils.js'; 2 | import {createTransformation, Future, crash, reject, resolve} from '../future.js'; 3 | 4 | function Eager(future){ 5 | var _this = this; 6 | _this.rec = noop; 7 | _this.rej = noop; 8 | _this.res = noop; 9 | _this.crashed = false; 10 | _this.rejected = false; 11 | _this.resolved = false; 12 | _this.value = null; 13 | _this.cancel = future._interpret(function Eager$crash(x){ 14 | _this.value = x; 15 | _this.crashed = true; 16 | _this.cancel = noop; 17 | _this.rec(x); 18 | }, function Eager$reject(x){ 19 | _this.value = x; 20 | _this.rejected = true; 21 | _this.cancel = noop; 22 | _this.rej(x); 23 | }, function Eager$resolve(x){ 24 | _this.value = x; 25 | _this.resolved = true; 26 | _this.cancel = noop; 27 | _this.res(x); 28 | }); 29 | } 30 | 31 | Eager.prototype = Object.create(Future.prototype); 32 | 33 | Eager.prototype._interpret = function Eager$interpret(rec, rej, res){ 34 | if(this.crashed) rec(this.value); 35 | else if(this.rejected) rej(this.value); 36 | else if(this.resolved) res(this.value); 37 | else{ 38 | this.rec = rec; 39 | this.rej = rej; 40 | this.res = res; 41 | } 42 | return this.cancel; 43 | }; 44 | 45 | export function earlyCrash(early, x){ 46 | early(crash(x)); 47 | } 48 | 49 | export function earlyReject(early, x){ 50 | early(reject(x)); 51 | } 52 | 53 | export function earlyResolve(early, x){ 54 | early(resolve(x)); 55 | } 56 | 57 | export function createParallelTransformation(name, rec, rej, res, prototype){ 58 | var ParallelTransformation = createTransformation(1, name, Object.assign({ 59 | run: function Parallel$run(early){ 60 | var eager = new Eager(this.$1); 61 | var transformation = new ParallelTransformation(this.context, eager); 62 | function Parallel$early(m){ early(m, transformation) } 63 | transformation.cancel = eager._interpret( 64 | function Parallel$rec(x){ rec(Parallel$early, x) }, 65 | function Parallel$rej(x){ rej(Parallel$early, x) }, 66 | function Parallel$res(x){ res(Parallel$early, x) } 67 | ); 68 | return transformation; 69 | }, 70 | }, prototype)); 71 | return ParallelTransformation; 72 | } 73 | -------------------------------------------------------------------------------- /src/internal/predicates.js: -------------------------------------------------------------------------------- 1 | import {FL} from './const.js'; 2 | 3 | export function isFunction(f){ 4 | return typeof f === 'function'; 5 | } 6 | 7 | export function isThenable(m){ 8 | return m instanceof Promise || m != null && isFunction(m.then); 9 | } 10 | 11 | export function isBoolean(f){ 12 | return typeof f === 'boolean'; 13 | } 14 | 15 | export function isNumber(f){ 16 | return typeof f === 'number'; 17 | } 18 | 19 | export function isUnsigned(n){ 20 | return (n === Infinity || isNumber(n) && n > 0 && n % 1 === 0); 21 | } 22 | 23 | export function isObject(o){ 24 | return o !== null && typeof o === 'object'; 25 | } 26 | 27 | export function isIterator(i){ 28 | return isObject(i) && isFunction(i.next); 29 | } 30 | 31 | export function isArray(x){ 32 | return Array.isArray(x); 33 | } 34 | 35 | export function hasMethod(method, x){ 36 | return x != null && isFunction(x[method]); 37 | } 38 | 39 | export function isFunctor(x){ 40 | return hasMethod(FL.map, x); 41 | } 42 | 43 | export function isAlt(x){ 44 | return isFunctor(x) && hasMethod(FL.alt, x); 45 | } 46 | 47 | export function isApply(x){ 48 | return isFunctor(x) && hasMethod(FL.ap, x); 49 | } 50 | 51 | export function isBifunctor(x){ 52 | return isFunctor(x) && hasMethod(FL.bimap, x); 53 | } 54 | 55 | export function isChain(x){ 56 | return isApply(x) && hasMethod(FL.chain, x); 57 | } 58 | -------------------------------------------------------------------------------- /src/internal/timing.js: -------------------------------------------------------------------------------- 1 | export var Undetermined = 0; 2 | export var Synchronous = 1; 3 | export var Asynchronous = 2; 4 | -------------------------------------------------------------------------------- /src/internal/utils.js: -------------------------------------------------------------------------------- 1 | export {default as show} from 'sanctuary-show'; 2 | 3 | /* c8 ignore next */ 4 | var setImmediate = typeof setImmediate === 'undefined' ? setImmediateFallback : setImmediate; 5 | 6 | export function noop(){} 7 | export function moop(){ return this } 8 | export function call(f, x){ return f(x) } 9 | 10 | export function setImmediateFallback(f, x){ 11 | return setTimeout(f, 0, x); 12 | } 13 | 14 | export function raise(x){ 15 | setImmediate(function rethrowErrorDelayedToEscapePromiseCatch(){ 16 | throw x; 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /src/lastly.js: -------------------------------------------------------------------------------- 1 | import {AndTransformation} from './and.js'; 2 | import { 3 | application, 4 | application1, 5 | createTransformation, 6 | future, 7 | Reject, 8 | Resolve, 9 | } from './future.js'; 10 | 11 | export var LastlyTransformation = createTransformation(1, 'lastly', { 12 | rejected: function LastlyAction$rejected(x){ 13 | return this.$1._transform(new AndTransformation(this.context, new Reject(this.context, x))); 14 | }, 15 | resolved: function LastlyAction$resolved(x){ 16 | return this.$1._transform(new AndTransformation(this.context, new Resolve(this.context, x))); 17 | }, 18 | }); 19 | 20 | export function lastly(cleanup){ 21 | var context1 = application1(lastly, future, arguments); 22 | return function lastly(program){ 23 | var context2 = application(2, lastly, future, arguments, context1); 24 | return program._transform(new LastlyTransformation(context2, cleanup)); 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /src/map-rej.js: -------------------------------------------------------------------------------- 1 | import {call} from './internal/utils.js'; 2 | import {createTransformation, Reject, application1, application, future, func} from './future.js'; 3 | 4 | export var MapRejTransformation = createTransformation(1, 'mapRej', { 5 | rejected: function MapRejTransformation$rejected(x){ 6 | return new Reject(this.context, call(this.$1, x)); 7 | }, 8 | }); 9 | 10 | export function mapRej(f){ 11 | var context1 = application1(mapRej, func, arguments); 12 | return function mapRej(m){ 13 | var context2 = application(2, mapRej, future, arguments, context1); 14 | return m._transform(new MapRejTransformation(context2, f)); 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /src/map.js: -------------------------------------------------------------------------------- 1 | import {FL} from './internal/const.js'; 2 | import {invalidArgumentOf} from './internal/error.js'; 3 | import {isFunctor} from './internal/predicates.js'; 4 | import {isFuture, MapTransformation, application1, application, func} from './future.js'; 5 | 6 | export var functor = {pred: isFunctor, error: invalidArgumentOf('have Functor implemented')}; 7 | 8 | export function map(f){ 9 | var context1 = application1(map, func, arguments); 10 | return function map(m){ 11 | var context2 = application(2, map, functor, arguments, context1); 12 | return isFuture(m) ? 13 | m._transform(new MapTransformation(context2, f)) : 14 | m[FL.map](f); 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /src/node.js: -------------------------------------------------------------------------------- 1 | import {wrapException} from './internal/error.js'; 2 | import {noop, call} from './internal/utils.js'; 3 | import {createInterpreter, application1, func} from './future.js'; 4 | 5 | export var Node = createInterpreter(1, 'node', function Node$interpret(rec, rej, res){ 6 | function Node$done(err, val){ 7 | cont = err ? function EncaseN3$rej(){ 8 | open = false; 9 | rej(err); 10 | } : function EncaseN3$res(){ 11 | open = false; 12 | res(val); 13 | }; 14 | if(open){ 15 | cont(); 16 | } 17 | } 18 | var open = false, cont = function(){ open = true }; 19 | try{ 20 | call(this.$1, Node$done); 21 | }catch(e){ 22 | rec(wrapException(e, this)); 23 | open = false; 24 | return noop; 25 | } 26 | cont(); 27 | return function Node$cancel(){ open = false }; 28 | }); 29 | 30 | export function node(f){ 31 | return new Node(application1(node, func, arguments), f); 32 | } 33 | -------------------------------------------------------------------------------- /src/pap.js: -------------------------------------------------------------------------------- 1 | import {createParallelTransformation, earlyCrash, earlyReject} from './internal/parallel.js'; 2 | import {noop} from './internal/utils.js'; 3 | import {typeError} from './internal/error.js'; 4 | import {isFunction} from './internal/predicates.js'; 5 | import {show} from './internal/utils.js'; 6 | import {MapTransformation, application1, application, future} from './future.js'; 7 | 8 | export var ParallelApTransformation = 9 | createParallelTransformation('pap', earlyCrash, earlyReject, noop, { 10 | resolved: function ParallelApTransformation$resolved(f){ 11 | if(isFunction(f)) return this.$1._transform(new MapTransformation(this.context, f)); 12 | throw typeError( 13 | 'pap expects the second Future to resolve to a Function\n' + 14 | ' Actual: ' + show(f) 15 | ); 16 | }, 17 | }); 18 | 19 | export function pap(mx){ 20 | var context1 = application1(pap, future, arguments); 21 | return function pap(mf){ 22 | var context2 = application(2, pap, future, arguments, context1); 23 | return mf._transform(new ParallelApTransformation(context2, mx)); 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /src/par.js: -------------------------------------------------------------------------------- 1 | import type from 'sanctuary-type-identifiers'; 2 | 3 | import {FL, namespace, version} from './internal/const.js'; 4 | import {invalidFutureArgument} from './internal/error.js'; 5 | import {captureContext} from './internal/debug.js'; 6 | import {nil} from './internal/list.js'; 7 | 8 | import {never, resolve, isFuture, MapTransformation} from './future.js'; 9 | import {ParallelApTransformation} from './pap.js'; 10 | import {RaceTransformation} from './race.js'; 11 | 12 | export function ConcurrentFuture(sequential){ 13 | this.sequential = sequential; 14 | } 15 | 16 | ConcurrentFuture.prototype = Object.create(Par.prototype); 17 | 18 | export function Par(sequential){ 19 | if(!isFuture(sequential)) throw invalidFutureArgument(Par.name, 0, sequential); 20 | return new ConcurrentFuture(sequential); 21 | } 22 | 23 | var $$type = namespace + '/ConcurrentFuture@' + version; 24 | var zeroInstance = new ConcurrentFuture(never); 25 | 26 | // Compliance with sanctuary-type-identifiers versions 1 and 2. 27 | // To prevent sanctuary-type-identifiers version 3 from identifying 28 | // 'Par' as being of the type denoted by $$type, we ensure that 29 | // Par.constructor.prototype is equal to Par. 30 | Par['@@type'] = $$type; 31 | Par.constructor = {prototype: Par}; 32 | 33 | Par[FL.of] = function Par$of(x){ 34 | return new ConcurrentFuture(resolve(x)); 35 | }; 36 | 37 | Par[FL.zero] = function Par$zero(){ 38 | return zeroInstance; 39 | }; 40 | 41 | Par.prototype['@@type'] = $$type; 42 | 43 | Par.prototype['@@show'] = function Par$show(){ 44 | return this.toString(); 45 | }; 46 | 47 | Par.prototype.toString = function Par$toString(){ 48 | return 'Par (' + this.sequential.toString() + ')'; 49 | }; 50 | 51 | Par.prototype[FL.map] = function Par$FL$map(f){ 52 | var context = captureContext( 53 | nil, 54 | 'a Fantasy Land dispatch to map via ConcurrentFuture', 55 | Par$FL$map 56 | ); 57 | return new ConcurrentFuture(this.sequential._transform(new MapTransformation(context, f))); 58 | }; 59 | 60 | Par.prototype[FL.ap] = function Par$FL$ap(other){ 61 | var context = captureContext( 62 | nil, 63 | 'a Fantasy Land dispatch to ap via ConcurrentFuture', 64 | Par$FL$ap 65 | ); 66 | return new ConcurrentFuture(other.sequential._transform( 67 | new ParallelApTransformation(context, this.sequential) 68 | )); 69 | }; 70 | 71 | Par.prototype[FL.alt] = function Par$FL$alt(other){ 72 | var context = captureContext( 73 | nil, 74 | 'a Fantasy Land dispatch to alt via ConcurrentFuture', 75 | Par$FL$alt 76 | ); 77 | return new ConcurrentFuture(other.sequential._transform( 78 | new RaceTransformation(context, this.sequential) 79 | )); 80 | }; 81 | 82 | export function isParallel(x){ 83 | return x instanceof ConcurrentFuture || type(x) === $$type; 84 | } 85 | -------------------------------------------------------------------------------- /src/parallel.js: -------------------------------------------------------------------------------- 1 | import {wrapException, invalidArgumentOf} from './internal/error.js'; 2 | import {isArray} from './internal/predicates.js'; 3 | import {noop} from './internal/utils.js'; 4 | import { 5 | createInterpreter, 6 | isFuture, 7 | resolve, 8 | application1, 9 | positiveInteger, 10 | application, 11 | } from './future.js'; 12 | 13 | function isFutureArray(xs){ 14 | if(!isArray(xs)) return false; 15 | for(var i = 0; i < xs.length; i++){ 16 | if(!isFuture(xs[i])) return false; 17 | } 18 | return true; 19 | } 20 | 21 | export var futureArray = { 22 | pred: isFutureArray, 23 | error: invalidArgumentOf('be an Array of valid Futures'), 24 | }; 25 | 26 | export var Parallel = createInterpreter(2, 'parallel', function Parallel$interpret(rec, rej, res){ 27 | var _this = this, futures = this.$2, length = futures.length; 28 | var max = Math.min(this.$1, length), cancels = new Array(length), out = new Array(length); 29 | var cursor = 0, running = 0, blocked = false, cont = noop; 30 | 31 | function Parallel$cancel(){ 32 | rec = noop; 33 | rej = noop; 34 | res = noop; 35 | cursor = length; 36 | for(var n = 0; n < length; n++) cancels[n] && cancels[n](); 37 | } 38 | 39 | function Parallel$run(idx){ 40 | running++; 41 | cancels[idx] = futures[idx]._interpret(function Parallel$rec(e){ 42 | cont = rec; 43 | cancels[idx] = noop; 44 | Parallel$cancel(); 45 | cont(wrapException(e, _this)); 46 | }, function Parallel$rej(reason){ 47 | cont = rej; 48 | cancels[idx] = noop; 49 | Parallel$cancel(); 50 | cont(reason); 51 | }, function Parallel$res(value){ 52 | cancels[idx] = noop; 53 | out[idx] = value; 54 | running--; 55 | if(cursor === length && running === 0) res(out); 56 | else if(blocked) Parallel$drain(); 57 | }); 58 | } 59 | 60 | function Parallel$drain(){ 61 | blocked = false; 62 | while(cursor < length && running < max) Parallel$run(cursor++); 63 | blocked = true; 64 | } 65 | 66 | Parallel$drain(); 67 | 68 | return Parallel$cancel; 69 | }); 70 | 71 | var emptyArray = resolve([]); 72 | 73 | export function parallel(max){ 74 | var context1 = application1(parallel, positiveInteger, arguments); 75 | return function parallel(ms){ 76 | var context2 = application(2, parallel, futureArray, arguments, context1); 77 | return ms.length === 0 ? emptyArray : new Parallel(context2, max, ms); 78 | }; 79 | } 80 | -------------------------------------------------------------------------------- /src/promise.js: -------------------------------------------------------------------------------- 1 | import {application1, future} from './future.js'; 2 | 3 | export function promise(m){ 4 | application1(promise, future, arguments); 5 | return new Promise(function promise$computation(res, rej){ 6 | m._interpret(rej, rej, res); 7 | }); 8 | } 9 | -------------------------------------------------------------------------------- /src/race.js: -------------------------------------------------------------------------------- 1 | import { 2 | createParallelTransformation, 3 | earlyCrash, 4 | earlyReject, 5 | earlyResolve, 6 | } from './internal/parallel.js'; 7 | import {application1, application, future} from './future.js'; 8 | 9 | export var RaceTransformation = 10 | createParallelTransformation('race', earlyCrash, earlyReject, earlyResolve, {}); 11 | 12 | export function race(left){ 13 | var context1 = application1(race, future, arguments); 14 | return function race(right){ 15 | var context2 = application(2, race, future, arguments, context1); 16 | return right._transform(new RaceTransformation(context2, left)); 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /src/reject-after.js: -------------------------------------------------------------------------------- 1 | import { 2 | any, 3 | application, 4 | application1, 5 | createInterpreter, 6 | never, 7 | positiveInteger, 8 | } from './future.js'; 9 | 10 | export var RejectAfter = 11 | createInterpreter(2, 'rejectAfter', function RejectAfter$interpret(rec, rej){ 12 | var id = setTimeout(rej, this.$1, this.$2); 13 | return function RejectAfter$cancel(){ clearTimeout(id) }; 14 | }); 15 | 16 | RejectAfter.prototype.extractLeft = function RejectAfter$extractLeft(){ 17 | return [this.$2]; 18 | }; 19 | 20 | function alwaysNever(_){ 21 | return never; 22 | } 23 | 24 | export function rejectAfter(time){ 25 | var context1 = application1(rejectAfter, positiveInteger, arguments); 26 | return time === Infinity ? alwaysNever : (function rejectAfter(value){ 27 | var context2 = application(2, rejectAfter, any, arguments, context1); 28 | return new RejectAfter(context2, time, value); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /src/seq.js: -------------------------------------------------------------------------------- 1 | import {invalidArgumentOf} from './internal/error.js'; 2 | import {application1} from './future.js'; 3 | import {isParallel} from './par.js'; 4 | 5 | var parallel = {pred: isParallel, error: invalidArgumentOf('be a ConcurrentFuture')}; 6 | 7 | export function seq(par){ 8 | application1(seq, parallel, arguments); 9 | return par.sequential; 10 | } 11 | -------------------------------------------------------------------------------- /src/swap.js: -------------------------------------------------------------------------------- 1 | import {createTransformation, Reject, Resolve, application1, future} from './future.js'; 2 | 3 | export var SwapTransformation = createTransformation(0, 'swap', { 4 | resolved: function SwapTransformation$resolved(x){ 5 | return new Reject(this.context, x); 6 | }, 7 | rejected: function SwapTransformation$rejected(x){ 8 | return new Resolve(this.context, x); 9 | }, 10 | }); 11 | 12 | export function swap(m){ 13 | var context = application1(swap, future, arguments); 14 | return m._transform(new SwapTransformation(context)); 15 | } 16 | -------------------------------------------------------------------------------- /src/value.js: -------------------------------------------------------------------------------- 1 | import {error} from './internal/error.js'; 2 | import {raise, show} from './internal/utils.js'; 3 | import {application1, application, func, future} from './future.js'; 4 | 5 | export function value(res){ 6 | var context1 = application1(value, func, arguments); 7 | return function value(m){ 8 | application(2, value, future, arguments, context1); 9 | function value$rej(x){ 10 | raise(error( 11 | 'Future#value was called on a rejected Future\n' + 12 | ' Rejection: ' + show(x) + '\n' + 13 | ' Future: ' + show(m) 14 | )); 15 | } 16 | return m._interpret(raise, value$rej, res); 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /test/arbitraries.js: -------------------------------------------------------------------------------- 1 | import jsv from 'jsverify'; 2 | import {Future, resolve, reject, Par, seq, extractLeft, extractRight} from '../index.js'; 3 | 4 | const noop = () => {}; 5 | const show = m => m.toString(); 6 | 7 | export const nil = jsv.elements([null, undefined]); 8 | 9 | const immediatelyResolve = x => { 10 | const m = Future((rej, res) => { setImmediate(res, x); return noop }); 11 | m.extractRight = () => [x]; 12 | return m; 13 | }; 14 | 15 | const immediatelyReject = x => { 16 | const m = Future((rej) => { setImmediate(rej, x); return noop }); 17 | m.extractLeft = () => [x]; 18 | return m; 19 | }; 20 | 21 | export function AsyncResolvedFutureArb (arb){ 22 | return arb.smap(immediatelyResolve, extractRight, show); 23 | } 24 | 25 | export function AsyncRejectedFutureArb (arb){ 26 | return arb.smap(immediatelyReject, extractLeft, show); 27 | } 28 | 29 | export function ResolvedFutureArb (arb){ 30 | return arb.smap(resolve, extractRight, show); 31 | } 32 | 33 | export function RejectedFutureArb (arb){ 34 | return arb.smap(reject, extractLeft, show); 35 | } 36 | 37 | export function FutureArb (larb, rarb){ 38 | return jsv.oneof( 39 | RejectedFutureArb(larb), 40 | ResolvedFutureArb(rarb), 41 | AsyncRejectedFutureArb(larb), 42 | AsyncResolvedFutureArb(rarb) 43 | ); 44 | } 45 | 46 | export const { 47 | any, 48 | anyFuture, 49 | anyRejectedFuture, 50 | anyResolvedFuture, 51 | anyNonFuture, 52 | anyFunction, 53 | } = jsv.letrec(tie => ({ 54 | anyRejectedFuture: jsv.oneof(AsyncRejectedFutureArb(tie('any')), RejectedFutureArb(tie('any'))), 55 | anyResolvedFuture: jsv.oneof(AsyncResolvedFutureArb(tie('any')), ResolvedFutureArb(tie('any'))), 56 | anyFuture: jsv.oneof(tie('anyRejectedFuture'), tie('anyResolvedFuture')), 57 | anyFunction: jsv.fn(tie('any')), 58 | anyNonFuture: jsv.oneof( 59 | jsv.number, 60 | jsv.string, 61 | jsv.bool, 62 | jsv.falsy, 63 | jsv.constant(new Error('Kapot')), 64 | jsv.constant(Future), 65 | jsv.constant(Par), 66 | tie('anyFunction') 67 | ), 68 | any: jsv.oneof( 69 | tie('anyNonFuture'), 70 | tie('anyFuture') 71 | ), 72 | })); 73 | 74 | export const anyParallel = anyFuture.smap(Par, seq, show); 75 | -------------------------------------------------------------------------------- /test/assertions.js: -------------------------------------------------------------------------------- 1 | import show from 'sanctuary-show'; 2 | import type from 'sanctuary-type-identifiers'; 3 | import {Future, isFuture} from '../index.js'; 4 | import {strictEqual, deepStrictEqual} from 'assert'; 5 | 6 | const states = ['pending', 'crashed', 'rejected', 'resolved']; 7 | 8 | export const equality = a => b => { 9 | strictEqual(show(a), show(b)); 10 | deepStrictEqual(a, b); 11 | return true; 12 | }; 13 | 14 | export const future = x => { 15 | equality(isFuture(x))(true); 16 | equality(type(x))(Future['@@type']); 17 | return true; 18 | }; 19 | 20 | export function makeEquivalence (equals){ 21 | return function equivalence (ma){ 22 | return function (mb){ 23 | let astate = 0, bstate = 0, val; 24 | return new Promise(function (pass, fail){ 25 | future(ma); 26 | future(mb); 27 | 28 | function twice (m, x, s1, s2){ 29 | fail(new Error( 30 | 'A Future ' + states[s2] + ' after already having ' + states[s1] + '.\n' + 31 | ' First: Future({ <' + states[s1] + '> ' + show(val) + ' })\n' + 32 | ' Second: Future({ <' + states[s1] + '> ' + show(x) + ' })\n' + 33 | ' Future: ' + m.toString() 34 | )); 35 | } 36 | 37 | function assertInnerEqual (a, b){ 38 | if(astate === bstate){ 39 | if(isFuture(a) && isFuture(b)){ 40 | equivalence(a)(b).then(pass, fail); 41 | return; 42 | } 43 | try { 44 | equals(a)(b); 45 | pass(true); 46 | } catch (e){ 47 | inequivalent('The inner values are not equal: ' + e.message); 48 | } 49 | }else{ 50 | inequivalent( 51 | 'One Future ' + states[astate] + ', and the other Future ' + states[bstate] 52 | ); 53 | } 54 | function inequivalent (message){ 55 | fail(new Error( 56 | '\n ' + ma.toString() + 57 | ' :: Future({ <' + states[astate] + '> ' + show(a) + ' })' + 58 | '\n is not equivalent to:\n ' + mb.toString() + 59 | ' :: Future({ <' + states[bstate] + '> ' + show(b) + ' })\n\n' + 60 | message 61 | )); 62 | } 63 | } 64 | 65 | ma._interpret(function (x){ 66 | if(astate > 0) twice(ma, x, astate, 1); 67 | else { 68 | astate = 1; 69 | if(bstate > 0) assertInnerEqual(x, val); 70 | else val = x; 71 | } 72 | }, function (x){ 73 | if(astate > 0) twice(ma, x, astate, 2); 74 | else { 75 | astate = 2; 76 | if(bstate > 0) assertInnerEqual(x, val); 77 | else val = x; 78 | } 79 | }, function (x){ 80 | if(astate > 0) twice(ma, x, astate, 3); 81 | else { 82 | astate = 3; 83 | if(bstate > 0) assertInnerEqual(x, val); 84 | else val = x; 85 | } 86 | }); 87 | 88 | mb._interpret(function (x){ 89 | if(bstate > 0) twice(mb, x, bstate, 1); 90 | else { 91 | bstate = 1; 92 | if(astate > 0) assertInnerEqual(val, x); 93 | else val = x; 94 | } 95 | }, function (x){ 96 | if(bstate > 0) twice(mb, x, bstate, 2); 97 | else { 98 | bstate = 2; 99 | if(astate > 0) assertInnerEqual(val, x); 100 | else val = x; 101 | } 102 | }, function (x){ 103 | if(bstate > 0) twice(mb, x, bstate, 3); 104 | else { 105 | bstate = 3; 106 | if(astate > 0) assertInnerEqual(val, x); 107 | else val = x; 108 | } 109 | }); 110 | }); 111 | }; 112 | }; 113 | } 114 | 115 | export const equivalence = makeEquivalence(equality); 116 | -------------------------------------------------------------------------------- /test/build/main.js: -------------------------------------------------------------------------------- 1 | import {noop, eq, test} from '../util/util.js'; 2 | import * as Fluture from '../../index.js'; 3 | import require from './require.js'; 4 | 5 | const Future = require('../../index.cjs'); 6 | 7 | test('exports the Future constructor by default', function (){ 8 | eq(typeof Future, 'function'); 9 | eq(Future(noop) instanceof Future, true); 10 | }); 11 | 12 | test('has a Future property that refers to itself for import destructuring', function (){ 13 | eq(Future.Future, Future); 14 | }); 15 | 16 | Object.keys(Fluture).forEach(key => { 17 | test('has a ' + key + ' property of type ' + typeof Fluture[key], function (){ 18 | eq(typeof Future[key], typeof Fluture[key]); 19 | }); 20 | }); 21 | 22 | test('exports Future with the correct name property', function (){ 23 | eq(Future.name, Fluture.Future.name); 24 | }); 25 | -------------------------------------------------------------------------------- /test/build/require.js: -------------------------------------------------------------------------------- 1 | import {createRequire} from 'module'; 2 | export default createRequire(import.meta.url); 3 | -------------------------------------------------------------------------------- /test/integration/main.js: -------------------------------------------------------------------------------- 1 | import { 2 | Future, 3 | after, 4 | ap, 5 | both, 6 | cache, 7 | chain, 8 | fork, 9 | map, 10 | parallel, 11 | race, 12 | rejectAfter, 13 | resolve, 14 | } from '../../index.js'; 15 | 16 | import {noop, error, assertResolved, eq, add, test} from '../util/util.js'; 17 | import {resolved, resolvedSlow} from '../util/futures.js'; 18 | 19 | function through (x, fs){ 20 | return fs.reduce(function (y, f){ 21 | return f(y); 22 | }, x); 23 | } 24 | 25 | test('is capable of joining', function (){ 26 | var m = through(resolve('a'), [ 27 | chain(function (x){ return chain(function (x){ return after(5)(x + 'c') })(after(5)(x + 'b')) }), 28 | chain(function (x){ return after(5)(x + 'd') }), 29 | chain(function (x){ return resolve(x + 'e') }), 30 | chain(function (x){ return after(5)(x + 'f') }), 31 | ]); 32 | return assertResolved(m, 'abcdef'); 33 | }); 34 | 35 | test('is capable of early termination', function (done){ 36 | var slow = Future(function (){ 37 | var id = setTimeout(done, 20, new Error('Not terminated')); 38 | return function (){ return clearTimeout(id) }; 39 | }); 40 | var m = through(slow, [race(slow), race(slow), race(slow), race(resolved)]); 41 | m._interpret(done, noop, noop); 42 | setTimeout(done, 40, null); 43 | }); 44 | 45 | test('cancels running actions when one early-terminates asynchronously', function (done){ 46 | var slow = Future(function (){ 47 | var id = setTimeout(done, 50, new Error('Not terminated')); 48 | return function (){ return clearTimeout(id) }; 49 | }); 50 | var m = through(slow, [race(slow), race(slow), race(slow), race(resolvedSlow)]); 51 | m._interpret(done, noop, noop); 52 | setTimeout(done, 100, null); 53 | }); 54 | 55 | test('does not run actions unnecessarily when one early-terminates synchronously', function (done){ 56 | var broken = Future(function (){ done(error) }); 57 | var m = through(resolvedSlow, [race(broken), race(broken), race(resolved)]); 58 | m._interpret(done, noop, function (){ return done() }); 59 | }); 60 | 61 | test('resolves the left-hand side first when running actions in parallel', function (){ 62 | var m = through(resolve(1), [ 63 | map(function (x){ return x }), 64 | chain(function (x){ return resolve(x) }), 65 | race(resolve(2)), 66 | ]); 67 | return assertResolved(m, 1); 68 | }); 69 | 70 | test('does not forget about actions to run after early termination', function (){ 71 | var m = through('a', [ 72 | after(100), 73 | race(after(20)('b')), 74 | map(function (x){ return (x + 'c') }), 75 | ]); 76 | return assertResolved(m, 'bc'); 77 | }); 78 | 79 | test('does not run early terminating actions twice, or cancel them', function (done){ 80 | var mock = Object.create(Future.prototype); 81 | mock._interpret = function (_, l, r){ return r(done()) || (function (){ return done(error) }) }; 82 | var m = through('a', [ 83 | after(30), 84 | map(function (x){ return (x + 'b') }), 85 | race(mock), 86 | ]); 87 | m._interpret(done, noop, noop); 88 | }); 89 | 90 | test('does not run concurrent computations twice', function (done){ 91 | var ran = false; 92 | var m = through(resolvedSlow, [ 93 | chain(function (){ return resolvedSlow }), 94 | race(Future(function (){ ran ? done(error) : (ran = true); return noop })), 95 | ]); 96 | m._interpret(done, done, function (){ return done() }); 97 | }); 98 | 99 | test('returns a cancel function which cancels all running actions', function (done){ 100 | var i = 0; 101 | var started = function (){ return void i++ }; 102 | var cancelled = function (){ return --i < 1 && done() }; 103 | var slow = Future(function (){ return started() || (function (){ return cancelled() }) }); 104 | var m = through(slow, [race(slow), race(slow), race(slow), race(slow)]); 105 | var cancel = m._interpret(done, noop, noop); 106 | eq(i, 5); 107 | cancel(); 108 | }); 109 | 110 | test('returns source when cast to String', function (){ 111 | const m = ap(resolve(22))(map(add)(resolve(20))); 112 | eq( 113 | String(m), 114 | 'ap (resolve (22)) (map (function (a){ return function (b){ return a + b } }) (resolve (20)))' 115 | ); 116 | }); 117 | 118 | test('returns an AST when cast to JSON', function (){ 119 | const m = ap(resolve(22))(map(add)(resolve(20))); 120 | eq( 121 | JSON.stringify(m), 122 | '{"$":"fluture/Future@5","kind":"interpreter","type":"transform","args":[' + 123 | '{"$":"fluture/Future@5","kind":"interpreter","type":"resolve","args":[20]},[' + 124 | '{"$":"fluture/Future@5","kind":"transformation","type":"ap","args":[' + 125 | '{"$":"fluture/Future@5","kind":"interpreter","type":"resolve","args":[22]}' + 126 | ']},' + 127 | '{"$":"fluture/Future@5","kind":"transformation","type":"map","args":[null]}' + 128 | ']' + 129 | ']}' 130 | ); 131 | }); 132 | 133 | test('does not produce issue #362', function (done){ 134 | const ma = cache(rejectAfter(1)(new Error)); 135 | const mb = map(noop)(parallel(Infinity)([ma, ma])); 136 | fork(() => done())(noop)(mb); 137 | }); 138 | 139 | test('does not produce issue #362 in a regular combinator', function (done){ 140 | const ma = cache(rejectAfter(1)(new Error)); 141 | const mb = map(noop)(both(ma)(ma)); 142 | fork(() => done())(noop)(mb); 143 | }); 144 | -------------------------------------------------------------------------------- /test/prop/0.algebra.js: -------------------------------------------------------------------------------- 1 | import show from 'sanctuary-show'; 2 | import {assertEqual, I, B, T, K} from '../util/util.js'; 3 | import {FutureArb, any as _x, anyFuture as _mx, f, g, _of, elements, property} from '../util/props.js'; 4 | import { 5 | alt, 6 | and, 7 | ap, 8 | bichain, 9 | bimap, 10 | cache, 11 | chain, 12 | chainRej, 13 | hook, 14 | lastly, 15 | map, 16 | mapRej, 17 | reject, 18 | resolve, 19 | swap, 20 | } from '../../index.js'; 21 | 22 | var _f = elements([f, g, I, resolve]); 23 | var _mf = _of(_f); 24 | var _fm = FutureArb(_f, _f).smap(function (m){ 25 | return function (x){ 26 | return bimap(T(x))(T(x))(m); 27 | }; 28 | }, function (f){ 29 | return bimap(K)(K)(f()); 30 | }, show); 31 | 32 | function eq (x){ 33 | return function (y){ 34 | return assertEqual(x, y); 35 | }; 36 | } 37 | 38 | property('alt associativity', _mx, _mx, _mx, function (a, b, c){ 39 | return eq(alt(a)(alt(b)(c)))(alt(alt(a)(b))(c)); 40 | }); 41 | 42 | property('alt distributivity with map', _mx, _mx, function (a, b){ 43 | return eq(map(f)(alt(a)(b)))(alt(map(f)(a))(map(f)(b))); 44 | }); 45 | 46 | property('and associativity', _mx, _mx, _mx, function (a, b, c){ 47 | return eq(and(a)(and(b)(c)))(and(and(a)(b))(c)); 48 | }); 49 | 50 | property('and distributivity with map', _mx, _mx, function (a, b){ 51 | return eq(map(f)(and(a)(b)))(and(map(f)(a))(map(f)(b))); 52 | }); 53 | 54 | property('ap composition using map', _mx, _mf, _mf, function (mx, mf, mg){ 55 | return eq(ap(mx)(ap(mf)(map(B)(mg))))(ap(ap(mx)(mf))(mg)); 56 | }); 57 | 58 | property('bichain associativity', _mx, _fm, _fm, function (m, f, g){ 59 | return eq(bichain(g)(g)(bichain(f)(f)(m)))(bichain(B(bichain(g)(g))(f))(B(bichain(g)(g))(f))(m)); 60 | }); 61 | 62 | property('bichain left identity on rejection', _x, _fm, _fm, function (x, f, g){ 63 | return eq(bichain(f)(g)(reject(x)))(f(x)); 64 | }); 65 | 66 | property('bichain left identity on resolution', _x, _fm, _fm, function (x, f, g){ 67 | return eq(bichain(f)(g)(resolve(x)))(g(x)); 68 | }); 69 | 70 | property('bichain right identity', _mx, function (m){ 71 | return eq(bichain(reject)(resolve)(m))(m); 72 | }); 73 | 74 | property('bimap identity', _mx, function (mx){ 75 | return eq(bimap(I)(I)(mx))(mx); 76 | }); 77 | 78 | property('bimap composition', _mx, _f, _f, _f, _f, function (mx, f, g, h, i){ 79 | return eq(bimap(B(f)(g))(B(h)(i))(mx))(bimap(f)(h)(bimap(g)(i)(mx))); 80 | }); 81 | 82 | property('cache idempotence', _mx, function (m){ 83 | return eq(cache(cache(m)))(cache(m)); 84 | }); 85 | 86 | property('chain associativity', _mx, _fm, _fm, function (m, f, g){ 87 | return eq(chain(g)(chain(f)(m)))(chain(B(chain(g))(f))(m)); 88 | }); 89 | 90 | property('chain left identity', _x, _fm, function (x, f){ 91 | return eq(chain(f)(resolve(x)))(f(x)); 92 | }); 93 | 94 | property('chain right identity', _mx, function (m){ 95 | return eq(chain(resolve)(m))(m); 96 | }); 97 | 98 | property('chainRej associativity', _mx, _fm, _fm, function (m, f, g){ 99 | return eq(chainRej(g)(chainRej(f)(m)))(chainRej(B(chainRej(g))(f))(m)); 100 | }); 101 | 102 | property('chainRej left identity', _x, _fm, function (x, f){ 103 | return eq(chainRej(f)(reject(x)))(f(x)); 104 | }); 105 | 106 | property('chainRej right identity', _mx, function (m){ 107 | return eq(chainRej(reject)(m))(m); 108 | }); 109 | 110 | property('hook identity', _mx, function (m){ 111 | return eq(hook(m)(resolve)(resolve))(m); 112 | }); 113 | 114 | property('lastly associativity', _mx, _mx, _mx, function (a, b, c){ 115 | return eq(lastly(a)(lastly(b)(c)))(lastly(lastly(a)(b))(c)); 116 | }); 117 | 118 | property('lastly distributivity with map', _mx, _mx, function (a, b){ 119 | return eq(map(f)(lastly(a)(b)))(lastly(map(f)(a))(map(f)(b))); 120 | }); 121 | 122 | property('map identity', _mx, function (m){ 123 | return eq(map(I)(m))(m); 124 | }); 125 | 126 | property('map composition', _mx, _f, _f, function (m, f, g){ 127 | return eq(map(B(f)(g))(m))(map(f)(map(g)(m))); 128 | }); 129 | 130 | property('mapRej identity', _mx, function (m){ 131 | return eq(mapRej(I)(m))(m); 132 | }); 133 | 134 | property('mapRej composition', _mx, _f, _f, function (m, f, g){ 135 | return eq(mapRej(B(f)(g))(m))(mapRej(f)(mapRej(g)(m))); 136 | }); 137 | 138 | property('resolve identity for ap', _mx, function (mx){ 139 | return eq(ap(mx)(resolve(I)))(mx); 140 | }); 141 | 142 | property('resolve homomorphism with ap', _x, function (x){ 143 | return eq(ap(resolve(x))(resolve(f)))(resolve(f(x))); 144 | }); 145 | 146 | property('resolve interchange with ap', _x, _mf, function (x, mf){ 147 | return eq(ap(resolve(x))(mf))(ap(mf)(resolve(T(x)))); 148 | }); 149 | 150 | property('swap self inverse', _mx, function (m){ 151 | return eq(swap(swap(m)))(m); 152 | }); 153 | -------------------------------------------------------------------------------- /test/prop/0.fantasy-land.js: -------------------------------------------------------------------------------- 1 | import FL from 'fantasy-laws'; 2 | import Z from 'sanctuary-type-classes'; 3 | import show from 'sanctuary-show'; 4 | import {Future, bimap} from '../../index.js'; 5 | import {assertEqual as eq, I, B, T, K, noop, STACKSIZE, test} from '../util/util.js'; 6 | import { 7 | FutureArb, 8 | _of, 9 | any as _x, 10 | anyFuture as _mx, 11 | constant as _k, 12 | elements, 13 | f, 14 | g, 15 | nat, 16 | property, 17 | suchthat, 18 | } from '../util/props.js'; 19 | 20 | var of = function (x){ 21 | return Z.of(Future, x); 22 | }; 23 | 24 | var _f = elements([f, g, I, of]); 25 | var _mf = _of(_f); 26 | var _fm = FutureArb(_f, _f).smap(function (m){ 27 | return function (x){ 28 | return bimap(T(x))(T(x))(m); 29 | }; 30 | }, function (f){ 31 | return bimap(K)(K)(f()); 32 | }, show); 33 | 34 | function testLaw (laws, typeclass, name){ 35 | var args = Array.from(arguments).slice(3); 36 | test(`${typeclass} ${name}`, laws[name].apply(null, args)); 37 | } 38 | 39 | testLaw(FL.Functor(eq), 'Functor', 'identity', _mx); 40 | testLaw(FL.Functor(eq), 'Functor', 'composition', _mx, _f, _f); 41 | 42 | testLaw(FL.Alt(eq), 'Alt', 'associativity', _mx, _mx, _mx); 43 | testLaw(FL.Alt(eq), 'Alt', 'distributivity', _mx, _mx, _f); 44 | 45 | testLaw(FL.Bifunctor(eq), 'Bifunctor', 'identity', _mx); 46 | testLaw(FL.Bifunctor(eq), 'Bifunctor', 'composition', _mx, _f, _f, _f, _f); 47 | 48 | testLaw(FL.Apply(eq), 'Apply', 'composition', _mf, _mf, _mx); 49 | 50 | testLaw(FL.Applicative(eq, Future), 'Applicative', 'identity', _mx); 51 | testLaw(FL.Applicative(eq, Future), 'Applicative', 'homomorphism', _f, _x); 52 | testLaw(FL.Applicative(eq, Future), 'Applicative', 'interchange', _mf, _x); 53 | 54 | testLaw(FL.Chain(eq), 'Chain', 'associativity', _mx, _fm, _fm); 55 | 56 | testLaw( 57 | FL.ChainRec(eq, Future), 58 | 'ChainRec', 59 | 'equivalence', 60 | _k(function (v){ return v < 1 }), 61 | _k(B(of)(function (v){ return v - 1 })), 62 | _k(of), 63 | suchthat(nat, function (x){ return x < 100 }) 64 | ); 65 | 66 | test('ChainRec stack-safety', function (){ 67 | var p = function (v){ return v > (STACKSIZE + 1) }; 68 | var d = of; 69 | var n = B(of)(function (v){ return v + 1 }); 70 | var a = Z.chainRec(Future, function (l, r, v){ return p(v) ? Z.map(r, d(v)) : Z.map(l, n(v)) }, 0); 71 | a._interpret(noop, noop, noop); 72 | }); 73 | 74 | testLaw(FL.Monad(eq, Future), 'Monad', 'leftIdentity', _fm, _mx); 75 | testLaw(FL.Monad(eq, Future), 'Monad', 'rightIdentity', _mx); 76 | 77 | property('map derived from ap and of', _mx, _f, function (m, f){ 78 | return eq(Z.map(f, m), Z.ap(of(f), m)); 79 | }); 80 | 81 | property('map derived from chain and of', _mx, _f, function (m, f){ 82 | return eq(Z.map(f, m), Z.chain(B(of)(f), m)); 83 | }); 84 | 85 | property('map derived from bimap', _mx, _f, function (m, f){ 86 | return eq(Z.map(f, m), Z.bimap(I, f, m)); 87 | }); 88 | 89 | property('ap derived from chain and map', _mx, _mf, function (mx, mf){ 90 | return eq(Z.ap(mf, mx), Z.chain(function (f){ return Z.map(f, mx) }, mf)); 91 | }); 92 | -------------------------------------------------------------------------------- /test/prop/1.fantasy-libs.js: -------------------------------------------------------------------------------- 1 | import * as R from 'ramda'; 2 | import Z from 'sanctuary-type-classes'; 3 | import {Future, ap, alt, map, bimap, chain, resolve, reject} from '../../index.js'; 4 | 5 | import {assertEqual} from '../util/util.js'; 6 | import {any, property, FutureArb, string, number, constant, anyFuture, oneof} from '../util/props.js'; 7 | 8 | function bang (x){ 9 | return x + '!'; 10 | } 11 | 12 | function compose (f, g){ 13 | return function (x){ 14 | return f(g(x)); 15 | }; 16 | } 17 | 18 | function square (x){ 19 | return x * x; 20 | } 21 | 22 | var stringNumberFuture = FutureArb(string, number); 23 | var stringSquareFuture = FutureArb(string, constant(square)); 24 | var make = oneof(constant(resolve), constant(reject)); 25 | 26 | property('Z.of(Future, x) = resolve(x)', any, function (x){ 27 | return assertEqual(Z.of(Future, x), resolve(x)); 28 | }); 29 | 30 | property('R.ap(mf, mx) = ap(mx)(mf)', stringSquareFuture, stringNumberFuture, function (mf, mx){ 31 | return assertEqual(R.ap(mf, mx), ap(mx)(mf)); 32 | }); 33 | 34 | property('Z.ap(mf, mx) = ap(mx)(mf)', stringSquareFuture, stringNumberFuture, function (mf, mx){ 35 | return assertEqual(Z.ap(mf, mx), ap(mx)(mf)); 36 | }); 37 | 38 | property('Z.alt(a, b) = alt(b)(a)', anyFuture, anyFuture, function (a, b){ 39 | return assertEqual(Z.alt(a, b), alt(b)(a)); 40 | }); 41 | 42 | property('R.map(f, mx) = map(f)(mx)', stringNumberFuture, function (mx){ 43 | return assertEqual(R.map(square, mx), map(square)(mx)); 44 | }); 45 | 46 | property('Z.map(f, mx) = map(f, mx)', stringNumberFuture, function (mx){ 47 | return assertEqual(Z.map(square, mx), map(square)(mx)); 48 | }); 49 | 50 | property('Z.bimap(f, g, mx) = bimap(f)(g)(mx)', stringNumberFuture, function (mx){ 51 | return assertEqual(Z.bimap(bang, square, mx), bimap(bang)(square)(mx)); 52 | }); 53 | 54 | property('R.chain(f, mx) = chain(f)(mx)', make, stringNumberFuture, function (g, mx){ 55 | var f = compose(f, square); 56 | return assertEqual(R.chain(f, mx), chain(f)(mx)); 57 | }); 58 | 59 | property('Z.chain(f, mx) = chain(f)(mx)', make, stringNumberFuture, function (g, mx){ 60 | var f = compose(f, square); 61 | return assertEqual(Z.chain(f, mx), chain(f)(mx)); 62 | }); 63 | -------------------------------------------------------------------------------- /test/prop/2.arbitrary.js: -------------------------------------------------------------------------------- 1 | import {assertEqual, I, B, K} from '../util/util.js'; 2 | import { 3 | any, 4 | anyFuture, 5 | anyRejectedFuture, 6 | anyResolvedFuture, 7 | constant, 8 | f, 9 | g, 10 | oneof, 11 | property, 12 | } from '../util/props.js'; 13 | import { 14 | after, 15 | and, 16 | bichain, 17 | bimap, 18 | chain, 19 | chainRej, 20 | coalesce, 21 | go, 22 | map, 23 | mapRej, 24 | reject, 25 | rejectAfter, 26 | resolve, 27 | swap, 28 | } from '../../index.js'; 29 | 30 | var make = oneof(constant(resolve), constant(reject)); 31 | 32 | function eq (a){ 33 | return function (b){ 34 | return assertEqual(a, b); 35 | }; 36 | } 37 | 38 | property('bichain(reject)(B(mk)(f))(m) = chain(B(mk)(f))(m)', make, anyFuture, function (mk, m){ 39 | return eq(bichain(reject)(B(mk)(f))(m))(chain(B(mk)(f))(m)); 40 | }); 41 | 42 | property('bichain(B(mk)(f))(resolve)(m) = chainRej(B(mk)(f))(m)', make, anyFuture, function (mk, m){ 43 | return eq(bichain(B(mk)(f))(resolve)(m))(chainRej(B(mk)(f))(m)); 44 | }); 45 | 46 | property('bichain(B(mk)(f))(B(mk)(g))(m) = chain(I)(coalesce(B(mk)(f))(B(mk)(g))(m))', make, anyFuture, function (mk, m){ 47 | return eq(bichain(B(mk)(f))(B(mk)(g))(m))(chain(I)(coalesce(B(mk)(f))(B(mk)(g))(m))); 48 | }); 49 | 50 | property('swap(m) = bichain(resolve)(reject)(m)', anyFuture, function (m){ 51 | return eq(swap(m))(bichain(resolve)(reject)(m)); 52 | }); 53 | 54 | property('swap(resolve(x)) = reject(x)', any, function (x){ 55 | return eq(swap(resolve(x)))(reject(x)); 56 | }); 57 | 58 | property('swap(reject(x)) = resolve(x)', any, function (x){ 59 | return eq(swap(reject(x)))(resolve(x)); 60 | }); 61 | 62 | property('Resolved m => chainRej(B(mk)(f))(m) = m', make, anyResolvedFuture, function (mk, m){ 63 | return eq(chainRej(B(mk)(f))(m))(m); 64 | }); 65 | 66 | property('Rejected m => chainRej(B(mk)(f))(m) = chain(B(mk)(f))(swap(m))', make, anyRejectedFuture, function (mk, m){ 67 | return eq(chainRej(B(mk)(f))(m))(chain(B(mk)(f))(swap(m))); 68 | }); 69 | 70 | property('Resolved m => chain(B(mk)(f))(m) = chainRej(B(mk)(f))(swap(m))', make, anyResolvedFuture, function (mk, m){ 71 | return eq(chain(B(mk)(f))(m))(chainRej(B(mk)(f))(swap(m))); 72 | }); 73 | 74 | property('after(1)(x) = resolve(x)', any, function (x){ 75 | return eq(after(1)(x))(resolve(x)); 76 | }); 77 | 78 | property('and(a)(b) = chain(K(a))(b)', anyFuture, anyFuture, function (a, b){ 79 | return eq(and(a)(b))(chain(K(a))(b)); 80 | }); 81 | 82 | property('coalesce(f)(g)(m) = chainRej(B(resolve)(f))(map(g)(m))', anyFuture, function (m){ 83 | return eq(coalesce(f)(g)(m))(chainRej(B(resolve)(f))(map(g)(m))); 84 | }); 85 | 86 | property('go(function*(){ return f(yield m) }) = map(f)(m)', anyFuture, function (m){ 87 | return eq(go(function*(){ return f(yield m) }))(map(f)(m)); 88 | }); 89 | 90 | property('mapRej(f)(m) = chainRej(B(reject)(f))(m)', anyFuture, function (m){ 91 | return eq(mapRej(f)(m))(chainRej(B(reject)(f))(m)); 92 | }); 93 | 94 | property('mapRej(f)(m) = bimap(f)(I)(m)', anyFuture, function (m){ 95 | return eq(mapRej(f)(m))(bimap(f)(I)(m)); 96 | }); 97 | 98 | property('mapRej(f)(m) = swap(map(f)(swap(m)))', anyFuture, function (m){ 99 | return eq(mapRej(f)(m))(swap(map(f)(swap(m)))); 100 | }); 101 | 102 | property('map(f)(m) = swap(mapRej(f)(swap(m)))', anyFuture, function (m){ 103 | return eq(map(f)(m))(swap(mapRej(f)(swap(m)))); 104 | }); 105 | 106 | property('rejectAfter(1)(x) = reject(x)', any, function (x){ 107 | return eq(rejectAfter(1)(x))(reject(x)); 108 | }); 109 | -------------------------------------------------------------------------------- /test/types/map.test-d.ts: -------------------------------------------------------------------------------- 1 | import {expectType} from 'tsd'; 2 | 3 | import * as fl from '../../index.js'; 4 | 5 | const resolved = fl.resolve (42); 6 | const rejected = fl.reject ('uh-oh'); 7 | 8 | const resolvedPar = fl.Par (resolved); 9 | const rejectedPar = fl.Par (rejected); 10 | 11 | // Standard usage on Future instances. 12 | expectType> (fl.map (String) (resolved)); 13 | expectType> (fl.map (String) (rejected)); 14 | 15 | // Standard usage on ConcurrentFuture instances. 16 | expectType> (fl.map (String) (resolvedPar)); 17 | expectType> (fl.map (String) (rejectedPar)); 18 | 19 | // Usage with pipe on Future instances (https://git.io/JLx3F). 20 | expectType> (resolved .pipe (fl.map (String))); 21 | expectType> (rejected .pipe (fl.map (String))); 22 | 23 | // Function parameter inference from the second argument in a pipe (https://git.io/JLxsX). 24 | expectType> (resolved .pipe (fl.map (x => x))); 25 | expectType> (rejected .pipe (fl.map (x => x))); 26 | -------------------------------------------------------------------------------- /test/unit/0.debug.js: -------------------------------------------------------------------------------- 1 | import {eq, error, assertStackTrace, K, I, test} from '../util/util.js'; 2 | import { 3 | debugMode, 4 | debug, 5 | captureContext, 6 | captureApplicationContext, 7 | captureStackTraceFallback, 8 | } from '../../src/internal/debug.js'; 9 | import {nil} from '../../src/internal/list.js'; 10 | 11 | test('debug does nothing but return its first argument by default', function (done){ 12 | debugMode(false); 13 | eq(debug(null, done, error), null); 14 | eq(debug(42, done, error), 42); 15 | done(); 16 | }); 17 | 18 | test('debug runs the given function when debugMode was enabled', function (){ 19 | var guard = {}; 20 | debugMode(true); 21 | eq(debug(null, K(guard)), guard); 22 | eq(debug(null, I, guard), guard); 23 | }); 24 | 25 | test('captureContext returns previous when debugMode is off', function (){ 26 | debugMode(false); 27 | var previous = {}; 28 | var x = captureContext(previous, 2, 3); 29 | eq(x, previous); 30 | }); 31 | 32 | test('captureContext returns a list with a context object', function (){ 33 | debugMode(true); 34 | var prev = nil; 35 | var tag = 'hello world'; 36 | var expectedName = ' from ' + tag + ':'; 37 | var ctx = captureContext(prev, tag); 38 | eq(typeof ctx, 'object'); 39 | eq(ctx.tail, prev); 40 | eq(typeof ctx.head, 'object'); 41 | eq(ctx.head.tag, tag); 42 | eq(ctx.head.name, expectedName); 43 | assertStackTrace(expectedName, ctx.head.stack); 44 | }); 45 | 46 | test('captureApplicationContext returns previous when debugMode is off', function (){ 47 | debugMode(false); 48 | var previous = {}; 49 | var x = captureApplicationContext(previous, 1, Math.sqrt); 50 | eq(x, previous); 51 | }); 52 | 53 | test('captureApplicationContext returns a list with a context object', function (){ 54 | debugMode(true); 55 | var prev = nil; 56 | var expectedName = ' from first application of sqrt:'; 57 | var ctx = captureApplicationContext(prev, 1, Math.sqrt); 58 | eq(typeof ctx, 'object'); 59 | eq(ctx.tail, prev); 60 | eq(typeof ctx.head, 'object'); 61 | eq(ctx.head.tag, 'first application of sqrt'); 62 | eq(ctx.head.name, expectedName); 63 | assertStackTrace(expectedName, ctx.head.stack); 64 | }); 65 | 66 | test('captureStackTraceFallback assigns a stack trace to the given object', function (){ 67 | debugMode(false); 68 | var o = {name: 'test'}; 69 | captureStackTraceFallback(o); 70 | assertStackTrace('test', o.stack); 71 | }); 72 | -------------------------------------------------------------------------------- /test/unit/0.error.js: -------------------------------------------------------------------------------- 1 | import {eq, assertStackTrace, error as mockError, noop, test} from '../util/util.js'; 2 | import {mock} from '../util/futures.js'; 3 | import {namespace, name, version} from '../../src/internal/const.js'; 4 | import {nil, cons, cat} from '../../src/internal/list.js'; 5 | import { 6 | error, 7 | typeError, 8 | invalidArgument, 9 | invalidContext, 10 | invalidArity, 11 | invalidFutureArgument, 12 | wrapException, 13 | contextToStackTrace, 14 | } from '../../src/internal/error.js'; 15 | 16 | function args (){ 17 | return arguments; 18 | } 19 | 20 | test('error constructs an Error', function (){ 21 | eq(error('hello'), new Error('hello')); 22 | }); 23 | 24 | test('typeError constructs a TypeError', function (){ 25 | eq(typeError('hello'), new TypeError('hello')); 26 | }); 27 | 28 | test('invalidArgument constructs a TypeError', function (){ 29 | eq(invalidArgument('Test', 1, 'foo', 'bar'), new TypeError( 30 | 'Test() expects its second argument to foo.\n Actual: "bar" :: String' 31 | )); 32 | }); 33 | 34 | test('invalidContext constructs a TypeError', function (){ 35 | eq(invalidContext('Test', 'foo'), new TypeError( 36 | 'Test() was invoked outside the context of a Future. You might want ' + 37 | 'to use a dispatcher instead\n Called on: "foo"' 38 | )); 39 | }); 40 | 41 | test('invalidArity constructs a TypeError', function (){ 42 | eq(invalidArity(noop, args('one', 2, 3, 4, 5, 6)), new TypeError( 43 | 'noop() expects to be called with a single argument per invocation\n' + 44 | ' Saw: 6 arguments\n' + 45 | ' First: "one" :: String\n' + 46 | ' Second: 2 :: Number\n' + 47 | ' Third: 3 :: Number\n' + 48 | ' Fourth: 4 :: Number\n' + 49 | ' Fifth: 5 :: Number\n' + 50 | ' Argument 6: 6 :: Number' 51 | )); 52 | }); 53 | 54 | var mockType = function (identifier){ 55 | return {'@@type': identifier, '@@show': function (){ 56 | return 'mockType("' + identifier + '")'; 57 | }}; 58 | }; 59 | 60 | test('invalidFutureArgument warns us when nothing seems wrong', function (){ 61 | var actual = invalidFutureArgument('Foo', 0, mockType(namespace + '/' + name + '@' + version)); 62 | eq(actual, new TypeError( 63 | 'Foo() expects its first argument to be a valid Future.\n' + 64 | 'Nothing seems wrong. Contact the Fluture maintainers.\n' + 65 | ' Actual: mockType("fluture/Future@5") :: Future' 66 | )); 67 | }); 68 | 69 | test('invalidFutureArgument warns us about Futures from other sources', function (){ 70 | var actual = invalidFutureArgument('Foo', 0, mockType('bobs-tinkershop/' + name + '@' + version)); 71 | eq(actual, new TypeError( 72 | 'Foo() expects its first argument to be a valid Future.\n' + 73 | 'The Future was not created by fluture. ' + 74 | 'Make sure you transform other Futures to fluture Futures. ' + 75 | 'Got a Future from bobs-tinkershop.\n' + 76 | ' See: https://github.com/fluture-js/Fluture#casting-futures\n' + 77 | ' Actual: mockType("bobs-tinkershop/Future@5") :: Future' 78 | )); 79 | }); 80 | 81 | test('invalidFutureArgument warns us about Futures from unnamed sources', function (){ 82 | var actual = invalidFutureArgument('Foo', 0, mockType(name)); 83 | eq(actual, new TypeError( 84 | 'Foo() expects its first argument to be a valid Future.\n' + 85 | 'The Future was not created by fluture. ' + 86 | 'Make sure you transform other Futures to fluture Futures. ' + 87 | 'Got an unscoped Future.\n' + 88 | ' See: https://github.com/fluture-js/Fluture#casting-futures\n' + 89 | ' Actual: mockType("Future") :: Future' 90 | )); 91 | }); 92 | 93 | test('invalidFutureArgument warns about older versions', function (){ 94 | var actual = invalidFutureArgument('Foo', 0, mockType(namespace + '/' + name + '@' + (version - 1))); 95 | eq(actual, new TypeError( 96 | 'Foo() expects its first argument to be a valid Future.\n' + 97 | 'The Future was created by an older version of fluture. ' + 98 | 'This means that one of the sources which creates Futures is outdated. ' + 99 | 'Update this source, or transform its created Futures to be compatible.\n' + 100 | ' See: https://github.com/fluture-js/Fluture#casting-futures\n' + 101 | ' Actual: mockType("fluture/Future@4") :: Future' 102 | )); 103 | }); 104 | 105 | test('invalidFutureArgument warns about newer versions', function (){ 106 | var actual = invalidFutureArgument('Foo', 0, mockType(namespace + '/' + name + '@' + (version + 1))); 107 | eq(actual, new TypeError( 108 | 'Foo() expects its first argument to be a valid Future.\n' + 109 | 'The Future was created by a newer version of fluture. ' + 110 | 'This means that one of the sources which creates Futures is outdated. ' + 111 | 'Update this source, or transform its created Futures to be compatible.\n' + 112 | ' See: https://github.com/fluture-js/Fluture#casting-futures\n' + 113 | ' Actual: mockType("fluture/Future@6") :: Future' 114 | )); 115 | }); 116 | 117 | test('wrapException converts any value to an Error', function (){ 118 | var evilValue = {}; 119 | evilValue.__defineGetter__('name', () => { throw new Error }); 120 | evilValue.__defineGetter__('stack', () => { throw new Error }); 121 | 122 | eq(wrapException(new Error('test'), mock) instanceof Error, true); 123 | eq(wrapException(new TypeError('test'), mock) instanceof Error, true); 124 | eq(wrapException('test', mock) instanceof Error, true); 125 | eq(wrapException({foo: 'bar'}, mock) instanceof Error, true); 126 | eq(wrapException(evilValue, mock) instanceof Error, true); 127 | eq(wrapException(null, mock) instanceof Error, true); 128 | eq(wrapException({crash: null}, mock) instanceof Error, true); 129 | }); 130 | 131 | test('wrapException creates an error which encompasses the given error', function (){ 132 | var m = Object.create(mock); 133 | m.context = cons({stack: 'hello'}, cons({stack: 'world'}, nil)); 134 | var e = wrapException(mockError, m); 135 | eq(e.name, 'Error'); 136 | eq(e.message, mockError.message); 137 | eq(e.reason, mockError); 138 | eq(e.context, m.context); 139 | eq(e.future, m); 140 | assertStackTrace('Error: Intentional error for unit testing', e.stack); 141 | 142 | var m2 = Object.create(mock); 143 | m2.context = cons({stack: 'foo'}, cons({stack: 'bar'}, nil)); 144 | var e2 = wrapException(e, m2); 145 | eq(e2.name, 'Error'); 146 | eq(e2.message, mockError.message); 147 | eq(e2.reason, mockError); 148 | eq(e2.context, cat(m.context, m2.context)); 149 | eq(e2.future, m); 150 | assertStackTrace('Error: Intentional error for unit testing', e2.stack); 151 | }); 152 | 153 | test('contextToStackTrace converts a nested context structure to a stack trace', function (){ 154 | eq(contextToStackTrace(cons({stack: 'hello'}, cons({stack: 'world'}, nil))), '\nhello\nworld'); 155 | }); 156 | -------------------------------------------------------------------------------- /test/unit/0.iteration.js: -------------------------------------------------------------------------------- 1 | import chai from 'chai'; 2 | import {test} from '../util/util.js'; 3 | import {Next, Done, isIteration} from '../../src/internal/iteration.js'; 4 | 5 | var expect = chai.expect; 6 | 7 | test('Next', function (){ 8 | var actual = Next(42); 9 | expect(isIteration(actual)).to.equal(true); 10 | expect(actual.done).to.equal(false); 11 | expect(actual.value).to.equal(42); 12 | }); 13 | 14 | test('Done', function (){ 15 | var actual = Done(42); 16 | expect(isIteration(actual)).to.equal(true); 17 | expect(actual.done).to.equal(true); 18 | expect(actual.value).to.equal(42); 19 | }); 20 | -------------------------------------------------------------------------------- /test/unit/0.list.js: -------------------------------------------------------------------------------- 1 | import {eq, test} from '../util/util.js'; 2 | import {nil, cons, reverse, cat} from '../../src/internal/list.js'; 3 | 4 | test('reverse', function (){ 5 | eq(reverse(nil), nil); 6 | eq(reverse(cons('a', nil)), cons('a', nil)); 7 | eq(reverse(cons('a', cons('b', nil))), cons('b', cons('a', nil))); 8 | eq(reverse(cons('a', cons('b', cons('c', nil)))), cons('c', cons('b', cons('a', nil)))); 9 | }); 10 | 11 | test('cat', function (){ 12 | eq(cat(nil, nil), nil); 13 | eq(cat(cons('a', nil), nil), cons('a', nil)); 14 | eq(cat(nil, cons('a', nil)), cons('a', nil)); 15 | eq( 16 | cat(cons('a', cons('b', nil)), cons('c', cons('d', nil))), 17 | cons('a', cons('b', cons('c', cons('d', nil)))) 18 | ); 19 | }); 20 | -------------------------------------------------------------------------------- /test/unit/0.predicates.js: -------------------------------------------------------------------------------- 1 | import chai from 'chai'; 2 | import {test, eq, noop} from '../util/util.js'; 3 | import Future from '../../index.js'; 4 | import * as util from '../../src/internal/predicates.js'; 5 | 6 | var expect = chai.expect; 7 | 8 | test('isThenable', function (){ 9 | var ps = [ 10 | Promise.resolve(1), 11 | new Promise(noop), 12 | {then: noop}, 13 | {then (a){ return a }}, 14 | {then (a, b){ return b }}, 15 | ]; 16 | 17 | var values = [NaN, 1, true, undefined, null, [], {}]; 18 | var xs = values.concat([noop]).concat(values.map(function (x){ return ({then: x}) })); 19 | 20 | ps.forEach(function (p){ return expect(util.isThenable(p)).to.equal(true) }); 21 | xs.forEach(function (x){ return expect(util.isThenable(x)).to.equal(false) }); 22 | }); 23 | 24 | test('isFunction', function (){ 25 | var xs = [NaN, 1, true, undefined, null, [], {}]; 26 | eq(util.isFunction(function (){}), true); 27 | eq(util.isFunction(Future), true); 28 | xs.forEach(function (x){ return expect(util.isFunction(x)).to.equal(false) }); 29 | }); 30 | 31 | test('isUnsigned', function (){ 32 | var is = [1, 2, 999999999999999, Infinity]; 33 | var xs = [NaN, 0, -0, -1, -999999999999999, -Infinity, '1', [], {}]; 34 | is.forEach(function (i){ return expect(util.isUnsigned(i)).to.equal(true) }); 35 | xs.forEach(function (x){ return expect(util.isUnsigned(x)).to.equal(false) }); 36 | }); 37 | 38 | test('isObject', function (){ 39 | function O (){} 40 | var os = [{}, {foo: 1}, Object.create(null), new O, []]; 41 | var xs = [1, true, NaN, null, undefined, '']; 42 | os.forEach(function (i){ return expect(util.isObject(i)).to.equal(true) }); 43 | xs.forEach(function (x){ return expect(util.isObject(x)).to.equal(false) }); 44 | }); 45 | 46 | test('isIterator', function (){ 47 | var is = [{next (){}}, {next (x){ return x }}, (function*(){}())]; 48 | var xs = [1, true, NaN, null, undefined, '', {}, {next: 1}]; 49 | is.forEach(function (i){ return expect(util.isIterator(i)).to.equal(true) }); 50 | xs.forEach(function (x){ return expect(util.isIterator(x)).to.equal(false) }); 51 | }); 52 | -------------------------------------------------------------------------------- /test/unit/0.utils.js: -------------------------------------------------------------------------------- 1 | import chai from 'chai'; 2 | import {test} from '../util/util.js'; 3 | import {setImmediateFallback} from '../../src/internal/utils.js'; 4 | 5 | var expect = chai.expect; 6 | 7 | test('setImmediateFallback', function (done){ 8 | var t = setTimeout(done, 25, new Error('Time is up')); 9 | setImmediateFallback(function (x){ 10 | expect(x).to.equal(42); 11 | clearTimeout(t); 12 | done(); 13 | }, 42); 14 | }); 15 | -------------------------------------------------------------------------------- /test/unit/1.future.js: -------------------------------------------------------------------------------- 1 | import Future from '../../index.js'; 2 | import {noop, eq, assertCrashed, assertResolved, error, assertValidFuture, test} from '../util/util.js'; 3 | import {testFunction, functionArg} from '../util/props.js'; 4 | 5 | testFunction('Future', Future, [functionArg], assertValidFuture); 6 | 7 | test('crashes when the computation throws an error', function (){ 8 | var m = Future(function (){ throw error }); 9 | return assertCrashed(m, error); 10 | }); 11 | 12 | test('crashes when the computation returns nonsense', function (){ 13 | var m = Future(function (){ return 1 }); 14 | return assertCrashed(m, new TypeError( 15 | 'The computation was expected to return a nullary cancellation function\n' + 16 | ' Actual: 1' 17 | )); 18 | }); 19 | 20 | test('does not crash when the computation returns a nullary function', function (){ 21 | return new Promise((res, rej) => { 22 | var m = Future(function (){ return function (){} }); 23 | m._interpret(rej, noop, noop); 24 | setTimeout(res, 20); 25 | }); 26 | }); 27 | 28 | test('settles using the last synchronously called continuation', function (){ 29 | var actual = Future(function (rej, res){ 30 | res(1); 31 | rej(2); 32 | res(3); 33 | return noop; 34 | }); 35 | return assertResolved(actual, 3); 36 | }); 37 | 38 | test('settles using the first asynchronously called continuation', function (){ 39 | var actual = Future(function (rej, res){ 40 | setTimeout(res, 10, 1); 41 | setTimeout(res, 50, 2); 42 | return noop; 43 | }); 44 | return assertResolved(actual, 1); 45 | }); 46 | 47 | test('stops continuations from being called after cancellation', function (){ 48 | return new Promise((res, rej) => { 49 | const fail = () => rej(error); 50 | Future(function (rej, res){ 51 | setTimeout(res, 20, 1); 52 | setTimeout(rej, 20, 1); 53 | return noop; 54 | }) 55 | ._interpret(rej, fail, fail)(); 56 | setTimeout(res, 25); 57 | }); 58 | }); 59 | 60 | test('cannot continue during cancellation (#216)', function (){ 61 | return new Promise((res, rej) => { 62 | const fail = () => rej(error); 63 | Future(function (rej, res){ 64 | return function (){ 65 | rej(); 66 | res(); 67 | }; 68 | }) 69 | ._interpret(rej, fail, fail)(); 70 | setTimeout(res, 20); 71 | }); 72 | }); 73 | 74 | test('stops cancellation from being called after continuations', function (){ 75 | return new Promise((res, rej) => { 76 | var m = Future(function (rej, res){ 77 | res(1); 78 | return function (){ rej(error) }; 79 | }); 80 | var cancel = m._interpret(rej, noop, noop); 81 | cancel(); 82 | res(); 83 | }); 84 | }); 85 | 86 | test('can be cast to String', function (){ 87 | var m = Future(function (rej, res){ res() }); 88 | eq(m.toString(), 'Future (function (rej, res){ res() })'); 89 | }); 90 | -------------------------------------------------------------------------------- /test/unit/1.is-future.js: -------------------------------------------------------------------------------- 1 | import {isFuture} from '../../index.js'; 2 | import {property, anyFuture, anyNonFuture} from '../util/props.js'; 3 | 4 | property('returns true about Futures', anyFuture, function (value){ 5 | return isFuture(value) === true; 6 | }); 7 | 8 | property('returns false about everything else', anyNonFuture, function (value){ 9 | return isFuture(value) === false; 10 | }); 11 | -------------------------------------------------------------------------------- /test/unit/1.is-never.js: -------------------------------------------------------------------------------- 1 | import {isNever, never} from '../../index.js'; 2 | import {any, property} from '../util/props.js'; 3 | import {eq, test} from '../util/util.js'; 4 | 5 | test('returns true about never', function (){ 6 | eq(isNever(never), true); 7 | }); 8 | 9 | property('returns false about everything else', any, function (value){ 10 | return isNever(value) === false; 11 | }); 12 | -------------------------------------------------------------------------------- /test/unit/1.never.js: -------------------------------------------------------------------------------- 1 | import {never} from '../../index.js'; 2 | import {assertValidFuture, noop, eq, test} from '../util/util.js'; 3 | 4 | test('is a Future', function (){ 5 | assertValidFuture(never); 6 | }); 7 | 8 | test('does nothing and returns a noop cancel function', function (){ 9 | var m = never; 10 | var cancel = m._interpret(noop, noop, noop); 11 | cancel(); 12 | }); 13 | 14 | test('returns the code to create the Never', function (){ 15 | var m = never; 16 | eq(m.toString(), 'never'); 17 | }); 18 | -------------------------------------------------------------------------------- /test/unit/1.pipe.js: -------------------------------------------------------------------------------- 1 | import {mock} from '../util/futures.js'; 2 | import {eq, throws, test} from '../util/util.js'; 3 | 4 | test('throws when not given a function', function (){ 5 | throws(function (){ 6 | mock.pipe(42); 7 | }, new TypeError( 8 | 'Future#pipe() expects its first argument to be a Function.\n' + 9 | ' Actual: 42 :: Number' 10 | )); 11 | }); 12 | 13 | test('transforms the Future using the given function', function (){ 14 | eq(mock.pipe(String), '(util.mock)'); 15 | }); 16 | -------------------------------------------------------------------------------- /test/unit/2.done.js: -------------------------------------------------------------------------------- 1 | import {done} from '../../index.js'; 2 | import {testFunction, functionArg, resolvedFutureArg} from '../util/props.js'; 3 | import {eq, isFunction, test} from '../util/util.js'; 4 | import {rejected, resolved} from '../util/futures.js'; 5 | 6 | testFunction('done', done, [functionArg, resolvedFutureArg], isFunction); 7 | 8 | test('passes the rejection value as first parameter', function (testDone){ 9 | done(function (x, y){ 10 | eq(x, 'rejected'); 11 | eq(y, undefined); 12 | testDone(); 13 | })(rejected); 14 | }); 15 | 16 | test('passes the resolution value as second parameter', function (testDone){ 17 | done(function (x, y){ 18 | eq(x, null); 19 | eq(y, 'resolved'); 20 | testDone(); 21 | })(resolved); 22 | }); 23 | -------------------------------------------------------------------------------- /test/unit/2.extract-left.js: -------------------------------------------------------------------------------- 1 | import {extractLeft} from '../../index.js'; 2 | import {testFunction, futureArg} from '../util/props.js'; 3 | import {isArray, test} from '../util/util.js'; 4 | import {mock} from '../util/futures.js'; 5 | 6 | testFunction('extractLeft', extractLeft, [futureArg], isArray); 7 | 8 | test('dispatches to #extractLeft', function (done){ 9 | var m = Object.create(mock); 10 | m.extractLeft = done; 11 | extractLeft(m); 12 | }); 13 | -------------------------------------------------------------------------------- /test/unit/2.extract-right.js: -------------------------------------------------------------------------------- 1 | import {extractRight} from '../../index.js'; 2 | import {testFunction, futureArg} from '../util/props.js'; 3 | import {isArray, test} from '../util/util.js'; 4 | import {mock} from '../util/futures.js'; 5 | 6 | testFunction('extractRight', extractRight, [futureArg], isArray); 7 | 8 | test('dispatches to #extractRight', function (done){ 9 | var m = Object.create(mock); 10 | m.extractRight = done; 11 | extractRight(m); 12 | }); 13 | -------------------------------------------------------------------------------- /test/unit/2.fork-catch.js: -------------------------------------------------------------------------------- 1 | import {forkCatch} from '../../index.js'; 2 | import {testFunction, functionArg, futureArg} from '../util/props.js'; 3 | import {eq, isFunction, error, noop, test} from '../util/util.js'; 4 | import {crashed, rejected, resolved} from '../util/futures.js'; 5 | 6 | testFunction('forkCatch', forkCatch, [functionArg, functionArg, functionArg, futureArg], isFunction); 7 | 8 | test('calls the first continuation with the crash exception', function (done){ 9 | function assertCrash (x){ 10 | eq(x, error); 11 | done(); 12 | } 13 | forkCatch(assertCrash)(noop)(noop)(crashed); 14 | }); 15 | 16 | test('calls the second continuation with the rejection reason', function (done){ 17 | function assertRejection (x){ 18 | eq(x, 'rejected'); 19 | done(); 20 | } 21 | forkCatch(noop)(assertRejection)(noop)(rejected); 22 | }); 23 | 24 | test('calls the third continuation with the resolution value', function (done){ 25 | function assertResolution (x){ 26 | eq(x, 'resolved'); 27 | done(); 28 | } 29 | forkCatch(noop)(noop)(assertResolution)(resolved); 30 | }); 31 | -------------------------------------------------------------------------------- /test/unit/2.fork.js: -------------------------------------------------------------------------------- 1 | import {fork} from '../../index.js'; 2 | import {testFunction, functionArg, futureArg} from '../util/props.js'; 3 | import {eq, isFunction, error, noop, itRaises, test} from '../util/util.js'; 4 | import {crashed, rejected, resolved} from '../util/futures.js'; 5 | 6 | testFunction('fork', fork, [functionArg, functionArg, futureArg], isFunction); 7 | 8 | itRaises('the crash exception', function (){ 9 | fork(noop)(noop)(crashed); 10 | }, error); 11 | 12 | test('calls the first continuation with the rejection reason', function (done){ 13 | function assertRejection (x){ 14 | eq(x, 'rejected'); 15 | done(); 16 | } 17 | fork(assertRejection)(noop)(rejected); 18 | }); 19 | 20 | test('calls the second continuation with the resolution value', function (done){ 21 | function assertResolution (x){ 22 | eq(x, 'resolved'); 23 | done(); 24 | } 25 | fork(noop)(assertResolution)(resolved); 26 | }); 27 | -------------------------------------------------------------------------------- /test/unit/2.promise.js: -------------------------------------------------------------------------------- 1 | import {promise} from '../../index.js'; 2 | import {testFunction, futureArg} from '../util/props.js'; 3 | import {noop, isThenable, eq, error, test} from '../util/util.js'; 4 | import {crashed, rejected, resolved} from '../util/futures.js'; 5 | 6 | process.addListener('unhandledRejection', noop); 7 | 8 | testFunction('promise', promise, [futureArg], isThenable); 9 | 10 | test('returns a Promise', function (){ 11 | var actual = promise(resolved); 12 | eq(actual instanceof Promise, true); 13 | }); 14 | 15 | test('rejects if the Future crashes', function (){ 16 | return promise(crashed).then( 17 | function (){ throw new Error('It resolved') }, 18 | function (x){ eq(x, error) } 19 | ); 20 | }); 21 | 22 | test('rejects if the Future rejects', function (){ 23 | return promise(rejected).then( 24 | function (){ throw new Error('It resolved') }, 25 | function (x){ eq(x, 'rejected') } 26 | ); 27 | }); 28 | 29 | test('resolves if the Future resolves', function (){ 30 | return promise(resolved).then( 31 | function (x){ eq(x, 'resolved') } 32 | ); 33 | }); 34 | -------------------------------------------------------------------------------- /test/unit/2.value.js: -------------------------------------------------------------------------------- 1 | import show from 'sanctuary-show'; 2 | import {value} from '../../index.js'; 3 | import {testFunction, functionArg, resolvedFutureArg} from '../util/props.js'; 4 | import {eq, isFunction, noop, itRaises, error, test} from '../util/util.js'; 5 | import {crashed, rejected, resolved} from '../util/futures.js'; 6 | 7 | testFunction('value', value, [functionArg, resolvedFutureArg], isFunction); 8 | 9 | itRaises('when the Future crashes', function (){ 10 | value(noop)(rejected); 11 | }, error); 12 | 13 | itRaises('when the Future rejects', function (){ 14 | value(noop)(crashed); 15 | }, new Error( 16 | 'Future#value was called on a rejected Future\n' + 17 | ' Rejection: "rejected"\n' + 18 | ' Future: ' + show(rejected) 19 | )); 20 | 21 | test('calls the continuation with the resolution value', function (done){ 22 | value(function (x){ 23 | eq(x, 'resolved'); 24 | done(); 25 | })(resolved); 26 | }); 27 | -------------------------------------------------------------------------------- /test/unit/3.after.js: -------------------------------------------------------------------------------- 1 | import {after, never} from '../../index.js'; 2 | import {eq, assertValidFuture, assertResolved, test, error} from '../util/util.js'; 3 | import {testFunction, positiveIntegerArg, anyArg} from '../util/props.js'; 4 | 5 | testFunction('after', after, [positiveIntegerArg, anyArg], assertValidFuture); 6 | 7 | test('returns Never when given Infinity', function (){ 8 | eq(after(Infinity)(1), never); 9 | }); 10 | 11 | test('resolves with the given value', function (){ 12 | return assertResolved(after(20)(1), 1); 13 | }); 14 | 15 | test('clears its internal timeout when cancelled', function (done){ 16 | const fail = () => done(error); 17 | after(20)(1)._interpret(done, fail, fail)(); 18 | setTimeout(done, 25); 19 | }); 20 | 21 | test('returns array with the value', function (){ 22 | eq(after(20)(1).extractRight(), [1]); 23 | }); 24 | 25 | test('returns the code to create the After when cast to String', function (){ 26 | eq(after(20)(1).toString(), 'after (20) (1)'); 27 | }); 28 | -------------------------------------------------------------------------------- /test/unit/3.attempt-p.js: -------------------------------------------------------------------------------- 1 | /* eslint prefer-promise-reject-errors: 0 */ 2 | 3 | import chai from 'chai'; 4 | import {attemptP, map, mapRej} from '../../index.js'; 5 | import {assertCrashed, assertRejected, assertResolved, assertValidFuture, error, noop, test} from '../util/util.js'; 6 | import {testFunction, functionArg} from '../util/props.js'; 7 | 8 | var expect = chai.expect; 9 | 10 | testFunction('encaseP', attemptP, [functionArg], assertValidFuture); 11 | 12 | test('crashes when the Promise generator throws', function (){ 13 | var m = attemptP(function (){ throw error }); 14 | return assertCrashed(m, error); 15 | }); 16 | 17 | test('crashes when the Promise generator does not return a Promise', function (){ 18 | var m = attemptP(noop); 19 | return assertCrashed(m, new TypeError( 20 | 'encaseP() expects the function it\'s given to return a Promise/Thenable\n' + 21 | ' Actual: undefined\n' + 22 | ' From calling: function (){}\n' + 23 | ' With: undefined' 24 | )); 25 | }); 26 | 27 | test('resolves with the resolution value of the returned Promise', function (){ 28 | var actual = attemptP(function (){ return Promise.resolve(1) }); 29 | return assertResolved(actual, 1); 30 | }); 31 | 32 | test('rejects with rejection reason of the returned Promise', function (){ 33 | var actual = attemptP(function (){ return Promise.reject(error) }); 34 | return assertRejected(actual, error); 35 | }); 36 | 37 | test('ensures no resolution happens after cancel', function (done){ 38 | const fail = () => done(error); 39 | var actual = attemptP(function (){ return Promise.resolve(1) }); 40 | actual._interpret(done, fail, fail)(); 41 | setTimeout(done, 20); 42 | }); 43 | 44 | test('ensures no rejection happens after cancel', function (done){ 45 | const fail = () => done(error); 46 | var actual = attemptP(function (){ return Promise.reject(1) }); 47 | actual._interpret(done, fail, fail)(); 48 | setTimeout(done, 20); 49 | }); 50 | 51 | test('crashes with errors that occur in rejection continuation', function (){ 52 | var m = map(function (){ throw error })(attemptP(function (){ return Promise.resolve(1) })); 53 | return assertCrashed(m, error); 54 | }); 55 | 56 | test('crashes with errors that occur in resolution continuation', function (){ 57 | var m = mapRej(function (){ throw error })(attemptP(function (){ return Promise.reject(1) })); 58 | return assertCrashed(m, error); 59 | }); 60 | 61 | test('returns the code to create the Future when cast to String', function (){ 62 | var f = function (){ return Promise.resolve(42) }; 63 | var m = attemptP(f); 64 | expect(m.toString()).to.equal('encaseP (' + f.toString() + ') (undefined)'); 65 | }); 66 | -------------------------------------------------------------------------------- /test/unit/3.attempt.js: -------------------------------------------------------------------------------- 1 | import chai from 'chai'; 2 | import {attempt, map} from '../../index.js'; 3 | import {test, assertCrashed, assertRejected, assertResolved, assertValidFuture, error} from '../util/util.js'; 4 | import {testFunction, functionArg} from '../util/props.js'; 5 | 6 | var expect = chai.expect; 7 | 8 | testFunction('encase', attempt, [functionArg], assertValidFuture); 9 | 10 | test('resolves with the return value of the function', function (){ 11 | var actual = attempt(function (){ return 1 }); 12 | return assertResolved(actual, 1); 13 | }); 14 | 15 | test('rejects with the exception thrown by the function', function (){ 16 | var actual = attempt(function (){ throw error }); 17 | return assertRejected(actual, error); 18 | }); 19 | 20 | test('does not swallow errors from subsequent maps and such', function (){ 21 | var m = map(function (){ throw error })(attempt(function (x){ return x })); 22 | return assertCrashed(m, error); 23 | }); 24 | 25 | test('returns the code to create the Future', function (){ 26 | var f = function (){}; 27 | var m = attempt(f); 28 | expect(m.toString()).to.equal('encase (' + f.toString() + ') (undefined)'); 29 | }); 30 | -------------------------------------------------------------------------------- /test/unit/3.cache.js: -------------------------------------------------------------------------------- 1 | import chai from 'chai'; 2 | import {Future, cache, resolve, reject, after, rejectAfter} from '../../index.js'; 3 | import {Crashed, Rejected, Resolved} from '../../src/cache.js'; 4 | import {test, assertCrashed, assertRejected, assertResolved, assertValidFuture, error, noop, onceOrError} from '../util/util.js'; 5 | import * as F from '../util/futures.js'; 6 | import {testFunction, futureArg} from '../util/props.js'; 7 | 8 | var expect = chai.expect; 9 | 10 | testFunction('cache', cache, [futureArg], assertValidFuture); 11 | 12 | test('interpret crashes if the underlying computation crashes', function (){ 13 | return assertCrashed(cache(F.crashed), error); 14 | }); 15 | 16 | test('interpret resolves with the resolution value resolve the given Future', function (){ 17 | return assertResolved(cache(resolve(1)), 1); 18 | }); 19 | 20 | test('interpret rejects with the rejection reason resolve the given Future', function (){ 21 | return assertRejected(cache(reject(error)), error); 22 | }); 23 | 24 | test('interpret only interprets its given Future once', function (){ 25 | var m = cache(Future(onceOrError(function (rej, res){ res(1); return noop }))); 26 | m._interpret(noop, noop, noop); 27 | m._interpret(noop, noop, noop); 28 | return assertResolved(m, 1); 29 | }); 30 | 31 | test('interpret crashes all consumers once a delayed crash happens', function (){ 32 | var m = cache(F.crashedSlow); 33 | var a = assertCrashed(m, error); 34 | var b = assertCrashed(m, error); 35 | var c = assertCrashed(m, error); 36 | return Promise.all([a, b, c]); 37 | }); 38 | 39 | test('interpret resolves all consumers once a delayed resolution happens', function (){ 40 | var m = cache(after(200)(1)); 41 | var a = assertResolved(m, 1); 42 | var b = assertResolved(m, 1); 43 | var c = assertResolved(m, 1); 44 | return Promise.all([a, b, c]); 45 | }); 46 | 47 | test('interpret rejects all consumers once a delayed rejection happens', function (){ 48 | var m = cache(rejectAfter(20)(error)); 49 | var a = assertRejected(m, error); 50 | var b = assertRejected(m, error); 51 | var c = assertRejected(m, error); 52 | return Promise.all([a, b, c]); 53 | }); 54 | 55 | test('interpret crashes all new consumers after a crash happened', function (){ 56 | var m = cache(F.crashed); 57 | m._interpret(noop, noop, noop); 58 | return assertCrashed(m, error); 59 | }); 60 | 61 | test('interpret rejects all new consumers after a rejection happened', function (){ 62 | var m = cache(reject('err')); 63 | m._interpret(noop, noop, noop); 64 | return assertRejected(m, 'err'); 65 | }); 66 | 67 | test('interpret it iinterpret nterprets the internal Future again when interpreted after having been cancelled', function (done){ 68 | var m = cache(Future(function (rej, res){ 69 | var o = {cancelled: false}; 70 | var id = setTimeout(res, 20, o); 71 | return function (){ o.cancelled = true; clearTimeout(id) }; 72 | })); 73 | var clear = m._interpret(done, noop, noop); 74 | setTimeout(function (){ 75 | clear(); 76 | m._interpret(done, noop, function (v){ return (expect(v).to.have.property('cancelled', false), done()) }); 77 | }, 10); 78 | }); 79 | 80 | test('interpret does not reset when one resolve multiple listeners is cancelled', function (done){ 81 | var m = cache(Future(function (rej, res){ 82 | setTimeout(res, 5, 1); 83 | return function (){ return done(new Error('Reset happened')) }; 84 | })); 85 | var cancel = m._interpret(done, noop, noop); 86 | m._interpret(done, noop, noop); 87 | cancel(); 88 | setTimeout(done, 20); 89 | }); 90 | 91 | test('interpret does not change when cancelled after settled', function (done){ 92 | var m = cache(Future(function (rej, res){ 93 | res(1); 94 | return function (){ return done(new Error('Cancelled after settled')) }; 95 | })); 96 | var cancel = m._interpret(done, noop, noop); 97 | setTimeout(function (){ 98 | cancel(); 99 | done(); 100 | }, 5); 101 | }); 102 | 103 | test('crash sets state to Crashed', function (){ 104 | var m = cache(Future(noop)); 105 | m.crash(1); 106 | expect(m._state).to.equal(Crashed); 107 | }); 108 | 109 | test('crash does nothing when state is resolved', function (){ 110 | var m = cache(Future(noop)); 111 | m.resolve(1); 112 | m.crash(2); 113 | expect(m._state).to.equal(Resolved); 114 | }); 115 | 116 | test('resolve does nothing when state is rejected', function (){ 117 | var m = cache(Future(noop)); 118 | m.reject(1); 119 | m.resolve(2); 120 | expect(m._state).to.equal(Rejected); 121 | }); 122 | 123 | test('reject does nothing when state is resolved', function (){ 124 | var m = cache(Future(noop)); 125 | m.resolve(1); 126 | m.reject(2); 127 | expect(m._state).to.equal(Resolved); 128 | }); 129 | 130 | test('_addToQueue does nothing when state is settled', function (){ 131 | var m = cache(Future(noop)); 132 | m.resolve(1); 133 | m._addToQueue(noop, noop); 134 | expect(m._queued).to.equal(0); 135 | }); 136 | 137 | test('_drainQueue is idempotent', function (){ 138 | var m = cache(resolve(1)); 139 | m._drainQueue(); 140 | m._drainQueue(); 141 | m._interpret(noop, noop, noop); 142 | m._drainQueue(); 143 | m._drainQueue(); 144 | }); 145 | 146 | test('run is idempotent', function (){ 147 | var m = cache(resolve(1)); 148 | m.run(); 149 | m.run(); 150 | }); 151 | 152 | test('reset is idempotent', function (){ 153 | var m = cache(resolve(1)); 154 | m.reset(); 155 | m._interpret(noop, noop, noop); 156 | m.reset(); 157 | m.reset(); 158 | }); 159 | 160 | test('reset cancels the underlying computation', function (done){ 161 | var m = cache(Future(function (){ return function (){ done() } })); 162 | m.run(); 163 | m.reset(); 164 | }); 165 | 166 | test('returns the code to create the Cache when cast to String', function (){ 167 | var m = cache(resolve(1)); 168 | var s = 'cache (resolve (1))'; 169 | expect(m.toString()).to.equal(s); 170 | }); 171 | 172 | test('extractLeft returns empty array for cold Cacheds', function (){ 173 | expect(cache(reject(1)).extractLeft()).to.deep.equal([]); 174 | }); 175 | 176 | test('extractLeft returns array with reason for rejected Cacheds', function (){ 177 | var m = cache(reject(1)); 178 | m.run(); 179 | expect(m.extractLeft()).to.deep.equal([1]); 180 | }); 181 | 182 | test('extractRight returns empty array for cold Cacheds', function (){ 183 | expect(cache(resolve(1)).extractRight()).to.deep.equal([]); 184 | }); 185 | 186 | test('extractRight returns array with value for resolved Cacheds', function (){ 187 | var m = cache(resolve(1)); 188 | m.run(); 189 | expect(m.extractRight()).to.deep.equal([1]); 190 | }); 191 | -------------------------------------------------------------------------------- /test/unit/3.crash.js: -------------------------------------------------------------------------------- 1 | import {crash} from '../../src/future.js'; 2 | import {eq, assertIsFuture, assertCrashed, test} from '../util/util.js'; 3 | import {testFunction, anyArg} from '../util/props.js'; 4 | 5 | testFunction('crash', crash, [anyArg], assertIsFuture); 6 | 7 | test('returns a crashed Future', function (){ 8 | return assertCrashed(crash(1), 1); 9 | }); 10 | 11 | test('can be shown as string', function (){ 12 | eq(crash(1).toString(), 'crash (1)'); 13 | }); 14 | -------------------------------------------------------------------------------- /test/unit/3.encase-p.js: -------------------------------------------------------------------------------- 1 | /* eslint prefer-promise-reject-errors: 0 */ 2 | 3 | import chai from 'chai'; 4 | import {encaseP} from '../../index.js'; 5 | import {test, assertCrashed, assertRejected, assertResolved, assertValidFuture, error, noop} from '../util/util.js'; 6 | import {testFunction, functionArg, anyArg} from '../util/props.js'; 7 | 8 | var expect = chai.expect; 9 | 10 | testFunction('encaseP', encaseP, [functionArg, anyArg], assertValidFuture); 11 | 12 | test('crashes when the Promise generator throws', function (){ 13 | var m = encaseP(function (){ throw error })(1); 14 | return assertCrashed(m, error); 15 | }); 16 | 17 | test('crashes when the Promise generator does not return a Promise', function (){ 18 | var m = encaseP(noop)(1); 19 | return assertCrashed(m, new TypeError( 20 | 'encaseP() expects the function it\'s given to return a Promise/Thenable\n' + 21 | ' Actual: undefined\n' + 22 | ' From calling: function (){}\n' + 23 | ' With: 1' 24 | )); 25 | }); 26 | 27 | test('resolves with the resolution value of the returned Promise', function (){ 28 | var actual = encaseP(function (x){ return Promise.resolve(x + 1) })(1); 29 | return assertResolved(actual, 2); 30 | }); 31 | 32 | test('rejects with rejection reason of the returned Promise', function (){ 33 | var actual = encaseP(function (){ return Promise.reject(error) })(1); 34 | return assertRejected(actual, error); 35 | }); 36 | 37 | test('ensures no resolution happens after cancel', function (done){ 38 | const fail = () => done(error); 39 | var actual = encaseP(function (x){ return Promise.resolve(x + 1) })(1); 40 | actual._interpret(done, fail, fail)(); 41 | setTimeout(done, 20); 42 | }); 43 | 44 | test('ensures no rejection happens after cancel', function (done){ 45 | const fail = () => done(error); 46 | var actual = encaseP(function (x){ return Promise.reject(x + 1) })(1); 47 | actual._interpret(done, fail, fail)(); 48 | setTimeout(done, 20); 49 | }); 50 | 51 | test('returns the code to create the Future when cast to String', function (){ 52 | var f = function (a){ return Promise.resolve(a) }; 53 | var m = encaseP(f)(null); 54 | expect(m.toString()).to.equal('encaseP (' + f.toString() + ') (null)'); 55 | }); 56 | -------------------------------------------------------------------------------- /test/unit/3.encase.js: -------------------------------------------------------------------------------- 1 | import chai from 'chai'; 2 | import {encase, map} from '../../index.js'; 3 | import {test, assertCrashed, assertRejected, assertResolved, assertValidFuture, error} from '../util/util.js'; 4 | import {testFunction, functionArg, anyArg} from '../util/props.js'; 5 | 6 | var expect = chai.expect; 7 | 8 | testFunction('encase', encase, [functionArg, anyArg], assertValidFuture); 9 | 10 | test('resolves with the return value of the function', function (){ 11 | var actual = encase(function (x){ return x + 1 })(1); 12 | return assertResolved(actual, 2); 13 | }); 14 | 15 | test('rejects with the exception thrown by the function', function (){ 16 | var actual = encase(function (a){ throw a, error })(1); 17 | return assertRejected(actual, error); 18 | }); 19 | 20 | test('does not swallow errors from subsequent maps and such', function (){ 21 | var m = map(function (){ throw error })(encase(function (x){ return x })(1)); 22 | return assertCrashed(m, error); 23 | }); 24 | 25 | test('returns the code to create the Future when cast to String', function (){ 26 | var f = function (a){ return void a }; 27 | var m = encase(f)(null); 28 | expect(m.toString()).to.equal('encase (' + f.toString() + ') (null)'); 29 | }); 30 | -------------------------------------------------------------------------------- /test/unit/3.go.js: -------------------------------------------------------------------------------- 1 | import chai from 'chai'; 2 | import {Future, go, resolve, after} from '../../index.js'; 3 | import {test, assertCrashed, assertResolved, assertValidFuture, error, noop, STACKSIZE} from '../util/util.js'; 4 | import * as F from '../util/futures.js'; 5 | import {testFunction, functionArg} from '../util/props.js'; 6 | 7 | var expect = chai.expect; 8 | 9 | testFunction('go', go, [functionArg], assertValidFuture); 10 | 11 | test('crashes when the given function throws an error', function (){ 12 | var m = go(function (){ throw error }); 13 | return assertCrashed(m, error); 14 | }); 15 | 16 | test('crashes when the given function does not return an interator', function (){ 17 | var m = go(function (){ return null }); 18 | return assertCrashed(m, new TypeError( 19 | 'go() expects its first argument to return an iterator, maybe you forgot the "*".\n' + 20 | ' Actual: null :: Null' 21 | )); 22 | }); 23 | 24 | test('crashes when iterator.next() throws an error', function (){ 25 | var m = go(function (){ return {next: () => { throw error }} }); 26 | return assertCrashed(m, error); 27 | }); 28 | 29 | test('crashes when the returned iterator does not return a valid iteration', function (){ 30 | var m = go(function (){ return {next: () => { return null }} }); 31 | return assertCrashed(m, new TypeError( 32 | 'The iterator did not return a valid iteration from iterator.next()\n' + 33 | ' Actual: null' 34 | )); 35 | }); 36 | 37 | test('crashes when the returned iterator produces something other than a Future', function (){ 38 | var m = go(function (){ return {next: () => { return {done: false, value: null} }} }); 39 | return assertCrashed(m, new TypeError( 40 | 'go() expects the value produced by the iterator to be a valid Future.\n' + 41 | ' Actual: null :: Null\n' + 42 | ' Tip: If you\'re using a generator, make sure you always yield a Future' 43 | )); 44 | }); 45 | 46 | test('crashes when the yielded Future crashes', function (){ 47 | var m = go(function*(){ yield F.crashed }); 48 | return assertCrashed(m, error); 49 | }); 50 | 51 | test('handles synchronous Futures', function (){ 52 | return assertResolved(go(function*(){ 53 | var a = yield resolve(1); 54 | var b = yield resolve(2); 55 | return a + b; 56 | }), 3); 57 | }); 58 | 59 | test('handles asynchronous Futures', function (){ 60 | return assertResolved(go(function*(){ 61 | var a = yield after(10)(1); 62 | var b = yield after(10)(2); 63 | return a + b; 64 | }), 3); 65 | }); 66 | 67 | test('does not mix state over multiple interpretations', function (){ 68 | var m = go(function*(){ 69 | var a = yield resolve(1); 70 | var b = yield after(10)(2); 71 | return a + b; 72 | }); 73 | return Promise.all([ 74 | assertResolved(m, 3), 75 | assertResolved(m, 3), 76 | ]); 77 | }); 78 | 79 | test('is stack safe', function (){ 80 | var gen = function*(){ 81 | var i = 0; 82 | while(i < STACKSIZE + 1){ yield resolve(i++) } 83 | return i; 84 | }; 85 | 86 | var m = go(gen); 87 | return assertResolved(m, STACKSIZE + 1); 88 | }); 89 | 90 | test('cancels the running operation when cancelled', function (done){ 91 | var cancel = go(function*(){ 92 | yield resolve(1); 93 | yield Future(function (){ return function (){ return done() } }); 94 | })._interpret(done, noop, noop); 95 | cancel(); 96 | }); 97 | 98 | test('returns the code to create the Go when cast to String', function (){ 99 | var f = function*(){}; 100 | var m = go(f); 101 | var s = 'go (' + f.toString() + ')'; 102 | expect(m.toString()).to.equal(s); 103 | }); 104 | -------------------------------------------------------------------------------- /test/unit/3.hook.js: -------------------------------------------------------------------------------- 1 | import chai from 'chai'; 2 | import {Future, hook, resolve, reject} from '../../index.js'; 3 | import {test, assertCrashed, assertIsFuture, assertRejected, assertResolved, error, itRaises, K, noop} from '../util/util.js'; 4 | import * as F from '../util/futures.js'; 5 | import {testFunction, futureArg, functionArg} from '../util/props.js'; 6 | 7 | var expect = chai.expect; 8 | 9 | testFunction('hook', hook, [futureArg, functionArg, functionArg], assertIsFuture); 10 | 11 | test('crashes when the disposal function does not return Future', function (){ 12 | var m = hook(F.resolved)(function (){ return 1 })(function (){ return F.resolved }); 13 | return assertCrashed(m, new TypeError( 14 | 'hook() expects the return value from the first function it\'s given to be a valid Future.\n' + 15 | ' Actual: 1 :: Number\n' + 16 | ' From calling: function (){ return 1 }\n' + 17 | ' With: "resolved"' 18 | )); 19 | }); 20 | 21 | test('crashes when the disposal function throws', function (){ 22 | var m = hook(F.resolved)(function (){ throw error })(function (){ return F.resolved }); 23 | return assertCrashed(m, error); 24 | }); 25 | 26 | test('crashes when the computation function does not return Future', function (){ 27 | var m = hook(F.resolved)(function (){ return F.resolved })(function (){ return 1 }); 28 | return assertCrashed(m, new TypeError( 29 | 'hook() expects the return value from the second function it\'s given to be a valid Future.\n' + 30 | ' Actual: 1 :: Number\n' + 31 | ' From calling: function (){ return 1 }\n' + 32 | ' With: "resolved"' 33 | )); 34 | }); 35 | 36 | test('crashes when the computation function throws', function (){ 37 | var m = hook(F.resolved)(function (){ return F.resolved })(function (){ throw error }); 38 | return assertCrashed(m, error); 39 | }); 40 | 41 | test('crashes when the disposal Future rejects', function (){ 42 | var rejected = hook(F.resolved)(function (){ return reject(1) })(function (){ return reject(2) }); 43 | var resolved = hook(F.resolved)(function (){ return reject(1) })(function (){ return resolve(2) }); 44 | return Promise.all([ 45 | assertCrashed(rejected, new Error('The disposal Future rejected with 1')), 46 | assertCrashed(resolved, new Error('The disposal Future rejected with 1')), 47 | ]); 48 | }); 49 | 50 | test('runs the first computation after the second, both with the resource', function (done){ 51 | var ran = false; 52 | hook(F.resolved)(function (x){ 53 | expect(x).to.equal('resolved'); 54 | return Future(function (rej, res){ res(done(ran ? null : new Error('Second did not run'))); return noop }); 55 | })(function (x){ 56 | expect(x).to.equal('resolved'); 57 | return Future(function (rej, res){ res(ran = true); return noop }); 58 | })._interpret(done, done, noop); 59 | }); 60 | 61 | test('runs the first even if the second rejects', function (done){ 62 | hook(F.resolved)(function (){ 63 | return Future(function (){ done(); return noop }); 64 | })(function (){ 65 | return reject(2); 66 | })._interpret(done, noop, noop); 67 | }); 68 | 69 | test('assumes the state resolve the second if the first resolves', function (){ 70 | var rejected = hook(F.resolved)(function (){ return resolve(1) })(function (){ return reject(2) }); 71 | var resolved = hook(F.resolved)(function (){ return resolve(1) })(function (){ return resolve(2) }); 72 | return Promise.all([ 73 | assertRejected(rejected, 2), 74 | assertResolved(resolved, 2), 75 | ]); 76 | }); 77 | 78 | test('does not hook after being cancelled', function (done){ 79 | const fail = () => done(error); 80 | hook(F.resolvedSlow)(function (){ return resolve('dispose') })(fail)._interpret(done, fail, fail)(); 81 | setTimeout(done, 25); 82 | }); 83 | 84 | test('does not reject after being cancelled', function (done){ 85 | const fail = () => done(error); 86 | hook(F.rejectedSlow)(function (){ return resolve('dispose') })(fail)._interpret(done, fail, fail)(); 87 | hook(F.resolved)(function (){ return resolve('dispose') })(function (){ return F.rejectedSlow })._interpret(done, fail, fail)(); 88 | setTimeout(done, 25); 89 | }); 90 | 91 | test('cancels acquire appropriately', function (done){ 92 | const fail = () => done(error); 93 | var acquire = Future(function (){ return function (){ return done() } }); 94 | var cancel = 95 | hook(acquire)(function (){ return resolve('dispose') })(function (){ return resolve('consume') }) 96 | ._interpret(done, fail, fail); 97 | setTimeout(cancel, 10); 98 | }); 99 | 100 | test('cancels consume appropriately', function (done){ 101 | const fail = () => done(error); 102 | var consume = Future(function (){ return function (){ return done() } }); 103 | var cancel = 104 | hook(F.resolved)(function (){ return resolve('dispose') })(function (){ return consume }) 105 | ._interpret(done, fail, fail); 106 | setTimeout(cancel, 10); 107 | }); 108 | 109 | test('cancels delayed consume appropriately', function (done){ 110 | const fail = () => done(error); 111 | var consume = Future(function (){ return function (){ return done() } }); 112 | var cancel = 113 | hook(F.resolvedSlow)(function (){ return resolve('dispose') })(function (){ return consume }) 114 | ._interpret(done, fail, fail); 115 | setTimeout(cancel, 25); 116 | }); 117 | 118 | test('does not cancel disposal', function (done){ 119 | const fail = () => done(error); 120 | var dispose = Future(function (){ return function (){ return done(error) } }); 121 | var cancel = 122 | hook(F.resolved)(function (){ return dispose })(function (){ return resolve('consume') }) 123 | ._interpret(done, fail, fail); 124 | setTimeout(cancel, 10); 125 | setTimeout(done, 50); 126 | }); 127 | 128 | test('does not cancel delayed dispose', function (done){ 129 | const fail = () => done(error); 130 | var dispose = Future(function (){ return function (){ return done(error) } }); 131 | var cancel = 132 | hook(F.resolved)(function (){ return dispose })(function (){ return F.resolvedSlow }) 133 | ._interpret(done, fail, fail); 134 | setTimeout(cancel, 50); 135 | setTimeout(done, 100); 136 | }); 137 | 138 | test('runs the disposal Future when cancelled after acquire', function (done){ 139 | const fail = () => done(error); 140 | var cancel = 141 | hook(F.resolved)(function (){ return Future(function (){ done(); return noop }) })(function (){ return F.resolvedSlow }) 142 | ._interpret(done, fail, fail); 143 | setTimeout(cancel, 10); 144 | }); 145 | 146 | itRaises('exceptions that occur after the Future was unsubscribed', function (done){ 147 | const fail = () => done(error); 148 | hook(F.resolved)(K(F.crashedSlow))(K(F.resolved))._interpret(function (){ 149 | done(new Error('Exception handler called')); 150 | }, fail, fail)(); 151 | }, error); 152 | 153 | test('returns the code which creates the same data-structure when cast to String', function (){ 154 | var a = resolve(1); 155 | var d = function (){ return resolve(2) }; 156 | var c = function (){ return resolve(3) }; 157 | var m = hook(a)(d)(c); 158 | var expected = 'hook (' + a.toString() + ') (' + d.toString() + ') (' + c.toString() + ')'; 159 | expect(m.toString()).to.equal(expected); 160 | }); 161 | -------------------------------------------------------------------------------- /test/unit/3.node.js: -------------------------------------------------------------------------------- 1 | import chai from 'chai'; 2 | import {node} from '../../index.js'; 3 | import {test, assertCrashed, assertRejected, assertResolved, assertValidFuture, error} from '../util/util.js'; 4 | import {testFunction, functionArg} from '../util/props.js'; 5 | 6 | var expect = chai.expect; 7 | 8 | testFunction('node', node, [functionArg], assertValidFuture); 9 | 10 | test('crashes when the function throws', function (){ 11 | var m = node(function (){ throw error }); 12 | return assertCrashed(m, error); 13 | }); 14 | 15 | test('rejects when the callback is called with (err)', function (){ 16 | var f = function (done){ return done(error) }; 17 | return assertRejected(node(f), error); 18 | }); 19 | 20 | test('resolves when the callback is called with (null, a)', function (){ 21 | var f = function (done){ return done(null, 'a') }; 22 | return assertResolved(node(f), 'a'); 23 | }); 24 | 25 | test('settles with the last synchronous call to done', function (){ 26 | var f = function (done){ done(null, 'a'); done(error); done(null, 'b') }; 27 | return assertResolved(node(f), 'b'); 28 | }); 29 | 30 | test('settles with the first asynchronous call to done', function (){ 31 | var f = function (done){ 32 | setTimeout(done, 10, null, 'a'); 33 | setTimeout(done, 50, null, 'b'); 34 | }; 35 | return assertResolved(node(f), 'a'); 36 | }); 37 | 38 | test('ensures no continuations are called after cancel', function (done){ 39 | const fail = () => done(error); 40 | var f = function (done){ return setTimeout(done, 5) }; 41 | node(f)._interpret(done, fail, fail)(); 42 | setTimeout(done, 20); 43 | }); 44 | 45 | test('returns the code to create the Future when cast to String', function (){ 46 | var f = function (a){ return void a }; 47 | var m = node(f); 48 | expect(m.toString()).to.equal('node (' + f.toString() + ')'); 49 | }); 50 | -------------------------------------------------------------------------------- /test/unit/3.parallel.js: -------------------------------------------------------------------------------- 1 | import chai from 'chai'; 2 | import {Future, parallel, resolve, reject, after} from '../../index.js'; 3 | import {test, promiseTimeout, assertCrashed, assertRejected, assertResolved, assertValidFuture, error, noop, repeat, STACKSIZE} from '../util/util.js'; 4 | import * as F from '../util/futures.js'; 5 | import {testFunction, positiveIntegerArg, futureArrayArg} from '../util/props.js'; 6 | 7 | var expect = chai.expect; 8 | 9 | testFunction('parallel', parallel, [positiveIntegerArg, futureArrayArg], assertValidFuture); 10 | 11 | test('crashes when one resolve the Futures crash', function (){ 12 | return assertCrashed(parallel(2)([F.resolved, F.crashed]), error); 13 | }); 14 | 15 | test('crashes when one resolve the Futures crash', function (){ 16 | return assertCrashed(parallel(2)([F.resolved, F.resolved, F.resolved, F.resolved, F.resolved, F.crashed]), error); 17 | }); 18 | 19 | test('throws when the Array contains something other than Futures', function (){ 20 | var xs = [NaN, {}, [], 1, 'a', new Date, undefined, null]; 21 | var fs = xs.map(function (x){ return function (){ return parallel(1)([x])._interpret(noop, noop, noop) } }); 22 | fs.forEach(function (f){ return expect(f).to.throw(TypeError, /Future/u) }); 23 | }); 24 | 25 | test('parallelizes execution', function (){ 26 | var actual = parallel(5)([ 27 | after(20)('a'), 28 | after(20)('b'), 29 | after(20)('c'), 30 | after(20)('d'), 31 | after(20)('e'), 32 | ]); 33 | return promiseTimeout(70, assertResolved(actual, ['a', 'b', 'c', 'd', 'e'])); 34 | }); 35 | 36 | test('limits parallelism to the given number', function (){ 37 | var running = 0; 38 | var m = Future(function (rej, res){ 39 | running++; 40 | if(running > 2){ return void rej(new Error('More than two running in parallel')) } 41 | setTimeout(function (){ 42 | running--; 43 | res('a'); 44 | }, 20); 45 | return noop; 46 | }); 47 | var actual = parallel(2)(repeat(8, m)); 48 | return assertResolved(actual, repeat(8, 'a')); 49 | }); 50 | 51 | test('runs all in parallel when given number larger than the array length', function (){ 52 | var actual = parallel(10)([ 53 | after(20)('a'), 54 | after(20)('b'), 55 | after(20)('c'), 56 | after(20)('d'), 57 | after(20)('e'), 58 | ]); 59 | return promiseTimeout(70, assertResolved(actual, ['a', 'b', 'c', 'd', 'e'])); 60 | }); 61 | 62 | test('can deal with synchronously resolving futures', function (){ 63 | return assertResolved(parallel(5)(repeat(10, resolve(1))), [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]); 64 | }); 65 | 66 | test('interprets the synchronous futures in the provided sequence', function (done){ 67 | var ns = Array.from({length: 10}, function (_, i){ return i }); 68 | var xs = []; 69 | var ms = ns.map(function (i){ 70 | return Future(function (rej, res){ 71 | xs.push(i); 72 | res(i); 73 | return noop; 74 | }); 75 | }); 76 | parallel(5)(ms)._interpret(done, noop, function (out){ 77 | expect(out).to.deep.equal(ns); 78 | expect(xs).to.deep.equal(ns); 79 | done(); 80 | }); 81 | }); 82 | 83 | test('interprets the asynchronous futures in the provided sequence', function (done){ 84 | var ns = Array.from({length: 10}, function (_, i){ return i }); 85 | var xs = []; 86 | var ms = ns.map(function (i){ 87 | return Future(function (rej, res){ 88 | xs.push(i); 89 | setTimeout(res, 10, i); 90 | return noop; 91 | }); 92 | }); 93 | parallel(5)(ms)._interpret(done, noop, function (out){ 94 | expect(out).to.deep.equal(ns); 95 | expect(xs).to.deep.equal(ns); 96 | done(); 97 | }); 98 | }); 99 | 100 | test('resolves to an empty array when given an empty array', function (){ 101 | return assertResolved(parallel(1)([]), []); 102 | }); 103 | 104 | test('runs all in parallel when given Infinity', function (){ 105 | var actual = parallel(Infinity)([ 106 | after(20)('a'), 107 | after(20)('b'), 108 | after(20)('c'), 109 | after(20)('d'), 110 | after(20)('e'), 111 | ]); 112 | return promiseTimeout(70, assertResolved(actual, ['a', 'b', 'c', 'd', 'e'])); 113 | }); 114 | 115 | test('rejects if one resolve the input rejects', function (){ 116 | var actual = parallel(2)([F.resolved, reject('err')]); 117 | return assertRejected(actual, 'err'); 118 | }); 119 | 120 | test('rejects with the first rejection value', function (){ 121 | return Promise.all([ 122 | assertRejected(parallel(2)([F.rejectedSlow, F.rejected]), 'rejected'), 123 | assertRejected(parallel(2)([F.rejected, F.rejectedSlow]), 'rejected'), 124 | ]); 125 | }); 126 | 127 | test('cancels Futures when cancelled', function (done){ 128 | var m = Future(function (){ return function (){ return done() } }); 129 | var cancel = parallel(1)([m])._interpret(done, noop, noop); 130 | setTimeout(cancel, 20); 131 | }); 132 | 133 | test('cancels only running Futures when cancelled', function (done){ 134 | var i = 0, j = 0; 135 | var m = Future(function (rej, res){ 136 | var x = setTimeout(function (x){ j += 1; res(x) }, 20, 1); 137 | 138 | return function (){ 139 | i += 1; 140 | clearTimeout(x); 141 | }; 142 | }); 143 | var cancel = parallel(2)([m, m, m, m])._interpret(done, noop, noop); 144 | setTimeout(function (){ 145 | cancel(); 146 | expect(i).to.equal(2); 147 | expect(j).to.equal(2); 148 | done(); 149 | }, 30); 150 | }); 151 | 152 | test('does not interpret any computations after one rejects', function (done){ 153 | var m = Future(function (){ done(error) }); 154 | parallel(2)([F.rejected, m])._interpret(done, noop, noop); 155 | done(); 156 | }); 157 | 158 | test('automatically cancels running computations when one rejects', function (done){ 159 | var m = Future(function (){ return function (){ done() } }); 160 | parallel(2)([m, F.rejected])._interpret(done, noop, noop); 161 | }); 162 | 163 | test('does not cancel settled computations (#123)', function (done){ 164 | var m1 = Object.create(F.mock); 165 | var m2 = Object.create(F.mock); 166 | 167 | m1._interpret = function (_, rej, res){ 168 | setTimeout(res, 10, 1); 169 | return function (){ return done(error) }; 170 | }; 171 | 172 | m2._interpret = function (_, rej){ 173 | setTimeout(rej, 50, 2); 174 | return function (){ return done(error) }; 175 | }; 176 | 177 | parallel(2)([m1, m2])._interpret(done, noop, noop); 178 | setTimeout(done, 100, null); 179 | }); 180 | 181 | test('does not resolve after being cancelled', function (done){ 182 | const fail = () => done(error); 183 | const cancel = parallel(1)([F.resolvedSlow, F.resolvedSlow]) 184 | ._interpret(done, fail, fail); 185 | cancel(); 186 | setTimeout(done, 100); 187 | }); 188 | 189 | test('does not reject after being cancelled', function (done){ 190 | const fail = () => done(error); 191 | const cancel = parallel(1)([F.rejectedSlow, F.rejectedSlow]) 192 | ._interpret(done, fail, fail); 193 | cancel(); 194 | setTimeout(done, 100); 195 | }); 196 | 197 | test('is stack safe (#130)', function (){ 198 | var ms = Array.from({length: STACKSIZE}, (_, i) => resolve(i)); 199 | var expected = Array.from({length: STACKSIZE}, (_, i) => i); 200 | return assertResolved(parallel(1)(ms), expected); 201 | }); 202 | 203 | test('returns the code to create the Parallel when cast to String', function (){ 204 | var m1 = parallel(Infinity)([resolve(1), resolve(2)]); 205 | var m2 = parallel(2)([resolve(1), resolve(2)]); 206 | var s1 = 'parallel (Infinity) ([resolve (1), resolve (2)])'; 207 | var s2 = 'parallel (2) ([resolve (1), resolve (2)])'; 208 | expect(m1.toString()).to.equal(s1); 209 | expect(m2.toString()).to.equal(s2); 210 | }); 211 | -------------------------------------------------------------------------------- /test/unit/3.reject-after.js: -------------------------------------------------------------------------------- 1 | import {rejectAfter, never} from '../../index.js'; 2 | import {eq, assertValidFuture, assertRejected, test, error} from '../util/util.js'; 3 | import {testFunction, positiveIntegerArg, anyArg} from '../util/props.js'; 4 | 5 | testFunction('rejectAfter', rejectAfter, [positiveIntegerArg, anyArg], assertValidFuture); 6 | 7 | test('returns Never when given Infinity', function (){ 8 | eq(rejectAfter(Infinity)(1), never); 9 | }); 10 | 11 | test('rejects with the given value', function (){ 12 | return assertRejected(rejectAfter(20)(1), 1); 13 | }); 14 | 15 | test('clears its internal timeout when cancelled', function (done){ 16 | const fail = () => done(error); 17 | rejectAfter(20)(1)._interpret(done, fail, fail)(); 18 | setTimeout(done, 25); 19 | }); 20 | 21 | test('returns array with the value', function (){ 22 | eq(rejectAfter(20)(1).extractLeft(), [1]); 23 | }); 24 | 25 | test('returns the code to create the After when cast to String', function (){ 26 | eq(rejectAfter(20)(1).toString(), 'rejectAfter (20) (1)'); 27 | }); 28 | -------------------------------------------------------------------------------- /test/unit/3.reject.js: -------------------------------------------------------------------------------- 1 | import {reject} from '../../index.js'; 2 | import {eq, assertValidFuture, assertRejected, test} from '../util/util.js'; 3 | import {testFunction, anyArg} from '../util/props.js'; 4 | 5 | testFunction('reject', reject, [anyArg], assertValidFuture); 6 | 7 | test('returns a rejected Future', function (){ 8 | return assertRejected(reject(1), 1); 9 | }); 10 | 11 | test('provides its reason to extractLeft()', function (){ 12 | eq(reject(1).extractLeft(), [1]); 13 | }); 14 | 15 | test('can be shown as string', function (){ 16 | eq(reject(1).toString(), 'reject (1)'); 17 | }); 18 | -------------------------------------------------------------------------------- /test/unit/3.resolve.js: -------------------------------------------------------------------------------- 1 | import {resolve} from '../../index.js'; 2 | import {eq, assertValidFuture, assertResolved, test} from '../util/util.js'; 3 | import {testFunction, anyArg} from '../util/props.js'; 4 | 5 | testFunction('resolve', resolve, [anyArg], assertValidFuture); 6 | 7 | test('returns a resolved Future', function (){ 8 | return assertResolved(resolve(1), 1); 9 | }); 10 | 11 | test('provides its reason to extractRight()', function (){ 12 | eq(resolve(1).extractRight(), [1]); 13 | }); 14 | 15 | test('can be shown as string', function (){ 16 | eq(resolve(1).toString(), 'resolve (1)'); 17 | }); 18 | -------------------------------------------------------------------------------- /test/unit/4.alt.js: -------------------------------------------------------------------------------- 1 | import Either from 'sanctuary-either'; 2 | import {Future, alt} from '../../index.js'; 3 | import {assertCrashed, eq, assertValidFuture, noop, assertResolved, assertRejected, error, test} from '../util/util.js'; 4 | import {crashed, rejected, resolved, rejectedSlow, resolvedSlow} from '../util/futures.js'; 5 | import {testFunction, altArg, futureArg} from '../util/props.js'; 6 | 7 | testFunction('alt', alt, [altArg, futureArg], assertValidFuture); 8 | 9 | test('chooses the resolved over the rejected Future', function (){ 10 | return Promise.all([ 11 | assertResolved(alt(crashed)(resolved), 'resolved'), 12 | assertResolved(alt(resolved)(resolved), 'resolved'), 13 | assertResolved(alt(rejected)(resolved), 'resolved'), 14 | assertResolved(alt(resolved)(rejected), 'resolved'), 15 | assertRejected(alt(rejected)(rejected), 'rejected'), 16 | assertResolved(alt(resolved)(resolvedSlow), 'resolvedSlow'), 17 | assertResolved(alt(resolvedSlow)(resolved), 'resolved'), 18 | assertRejected(alt(rejected)(rejectedSlow), 'rejected'), 19 | assertRejected(alt(rejectedSlow)(rejected), 'rejectedSlow'), 20 | assertCrashed(alt(rejected)(crashed), error), 21 | assertCrashed(alt(resolved)(crashed), error), 22 | assertCrashed(alt(crashed)(rejected), error), 23 | ]); 24 | }); 25 | 26 | test('dispatches to Fantasy Land alt', function (){ 27 | eq(alt(Either.Right(42))(Either.Left(42)), Either.Right(42)); 28 | }); 29 | 30 | test('cancels the running Future', function (done){ 31 | var m = Future(function (){ return function (){ return done() } }); 32 | var cancel = alt(m)(m)._interpret(done, noop, noop); 33 | cancel(); 34 | }); 35 | 36 | test('displays correctly as string', function (){ 37 | eq(alt(rejected)(resolved).toString(), 'alt (reject ("rejected")) (resolve ("resolved"))'); 38 | }); 39 | 40 | -------------------------------------------------------------------------------- /test/unit/4.and.js: -------------------------------------------------------------------------------- 1 | import {Future, and} from '../../index.js'; 2 | import {assertCrashed, eq, assertValidFuture, noop, assertResolved, assertRejected, error, test} from '../util/util.js'; 3 | import {crashed, rejected, resolved, rejectedSlow, resolvedSlow} from '../util/futures.js'; 4 | import {testFunction, futureArg} from '../util/props.js'; 5 | 6 | testFunction('and', and, [futureArg, futureArg], assertValidFuture); 7 | 8 | test('chooses the rejected over the resolved Future', function (){ 9 | return Promise.all([ 10 | assertResolved(and(resolved)(resolved), 'resolved'), 11 | assertRejected(and(rejected)(resolved), 'rejected'), 12 | assertRejected(and(resolved)(rejected), 'rejected'), 13 | assertRejected(and(rejected)(rejected), 'rejected'), 14 | assertResolved(and(resolved)(resolvedSlow), 'resolved'), 15 | assertResolved(and(resolvedSlow)(resolved), 'resolvedSlow'), 16 | assertRejected(and(rejected)(rejectedSlow), 'rejectedSlow'), 17 | assertRejected(and(rejectedSlow)(rejected), 'rejected'), 18 | assertCrashed(and(resolved)(crashed), error), 19 | assertCrashed(and(rejected)(crashed), error), 20 | assertCrashed(and(crashed)(resolved), error), 21 | ]); 22 | }); 23 | 24 | test('cancels the running Future', function (done){ 25 | var m = Future(function (){ return function (){ return done() } }); 26 | var cancel = and(m)(m)._interpret(done, noop, noop); 27 | cancel(); 28 | }); 29 | 30 | test('displays correctly as string', function (){ 31 | eq(and(rejected)(resolved).toString(), 'and (reject ("rejected")) (resolve ("resolved"))'); 32 | }); 33 | -------------------------------------------------------------------------------- /test/unit/4.ap.js: -------------------------------------------------------------------------------- 1 | import Either from 'sanctuary-either'; 2 | import {Future, ap, resolve, reject, after} from '../../index.js'; 3 | import {assertCrashed, assertRejected, assertResolved, assertValidFuture, noop, add, eq, bang, test} from '../util/util.js'; 4 | import {testFunction, applyArg, futureArg} from '../util/props.js'; 5 | 6 | testFunction('ap', ap, [applyArg, futureArg], assertValidFuture); 7 | 8 | test('dispatches to Fantasy Land ap', function (){ 9 | eq(ap(Either.Right('hello'))(Either.Right(bang)), Either.Right('hello!')); 10 | }); 11 | 12 | test('crashes when the other does not resolve to a Function', function (){ 13 | var m = ap(resolve(1))(resolve(null)); 14 | return assertCrashed(m, new TypeError( 15 | 'ap expects the second Future to resolve to a Function\n' + 16 | ' Actual: null' 17 | )); 18 | }); 19 | 20 | test('applies the Function on the right to the value on the left', function (){ 21 | return Promise.all([ 22 | assertResolved(ap(resolve(1))(resolve(add(1))), 2), 23 | assertRejected(ap(resolve(add(1)))(reject('err')), 'err'), 24 | assertRejected(ap(reject('err'))(resolve(add(1))), 'err'), 25 | assertResolved(ap(after(20)(1))(resolve(add(1))), 2), 26 | assertResolved(ap(resolve(1))(after(20)(add(1))), 2), 27 | ]); 28 | }); 29 | 30 | test('cancels the left Future if cancel is called while it is running', function (done){ 31 | var left = Future(function (){ return function (){ return done() } }); 32 | var right = resolve(add(1)); 33 | var cancel = ap(left)(right)._interpret(done, noop, noop); 34 | cancel(); 35 | }); 36 | 37 | test('cancels the right Future if cancel is called while it is running', function (done){ 38 | var left = resolve(1); 39 | var right = Future(function (){ return function (){ return done() } }); 40 | var cancel = ap(left)(right)._interpret(done, noop, noop); 41 | cancel(); 42 | }); 43 | 44 | test('displays correctly as string', function (){ 45 | eq(ap(resolve(1))(resolve(2)).toString(), 'ap (resolve (1)) (resolve (2))'); 46 | }); 47 | -------------------------------------------------------------------------------- /test/unit/4.bichain.js: -------------------------------------------------------------------------------- 1 | import {bichain} from '../../index.js'; 2 | import {test, assertCrashed, assertRejected, assertResolved, assertValidFuture, eq, throwing, error} from '../util/util.js'; 3 | import {testFunction, functionArg, futureArg} from '../util/props.js'; 4 | import {resolvedSlow, resolved, rejected, rejectedSlow} from '../util/futures.js'; 5 | import {resolve, reject} from '../../index.js'; 6 | 7 | testFunction('bichain', bichain, [functionArg, functionArg, futureArg], assertValidFuture); 8 | 9 | test('runs a bichain transformation on Futures', function (){ 10 | return Promise.all([ 11 | assertResolved(bichain(reject)(resolve)(resolved), 'resolved'), 12 | assertResolved(bichain(reject)(resolve)(resolvedSlow), 'resolvedSlow'), 13 | assertRejected(bichain(resolve)(reject)(resolved), 'resolved'), 14 | assertRejected(bichain(resolve)(reject)(resolvedSlow), 'resolvedSlow'), 15 | assertResolved(bichain(resolve)(reject)(rejected), 'rejected'), 16 | assertResolved(bichain(resolve)(reject)(rejectedSlow), 'rejectedSlow'), 17 | assertRejected(bichain(reject)(resolve)(rejected), 'rejected'), 18 | assertRejected(bichain(reject)(resolve)(rejectedSlow), 'rejectedSlow'), 19 | assertCrashed(bichain(throwing)(reject)(rejected), error), 20 | assertCrashed(bichain(resolve)(throwing)(resolved), error), 21 | ]); 22 | }); 23 | 24 | test('displays correctly as string', function (){ 25 | eq(bichain(resolve)(reject)(resolved).toString(), 'bichain (' + resolve.toString() + ') (' + reject.toString() + ') (resolve ("resolved"))'); 26 | }); 27 | -------------------------------------------------------------------------------- /test/unit/4.bimap.js: -------------------------------------------------------------------------------- 1 | import Either from 'sanctuary-either'; 2 | import {bimap} from '../../index.js'; 3 | import {test, assertCrashed, assertRejected, assertResolved, assertValidFuture, I, bang, eq, throwing, error} from '../util/util.js'; 4 | import {testFunction, functionArg, bifunctorArg} from '../util/props.js'; 5 | import {resolved, rejected} from '../util/futures.js'; 6 | 7 | testFunction('bimap', bimap, [functionArg, functionArg, bifunctorArg], assertValidFuture); 8 | 9 | test('runs a bimap transformation on Futures', function (){ 10 | return Promise.all([ 11 | assertRejected(bimap(bang)(I)(rejected), 'rejected!'), 12 | assertResolved(bimap(I)(bang)(resolved), 'resolved!'), 13 | assertCrashed(bimap(throwing)(I)(rejected), error), 14 | assertCrashed(bimap(I)(throwing)(resolved), error), 15 | ]); 16 | }); 17 | 18 | test('dispatches to Fantasy Land bimap otherwise', function (){ 19 | eq(bimap(I)(bang)(Either.Right('hello')), Either.Right('hello!')); 20 | }); 21 | 22 | test('displays correctly as string', function (){ 23 | eq(bimap(I)(bang)(resolved).toString(), 'bimap (' + I.toString() + ') (' + bang.toString() + ') (resolve ("resolved"))'); 24 | }); 25 | -------------------------------------------------------------------------------- /test/unit/4.both.js: -------------------------------------------------------------------------------- 1 | import {Future, both, node, done} from '../../index.js'; 2 | import {test, assertCrashed, assertRejected, assertResolved, assertValidFuture, error, noop, eq} from '../util/util.js'; 3 | import {crashed, rejected, resolved, crashedSlow, rejectedSlow, resolvedSlow} from '../util/futures.js'; 4 | import {testFunction, futureArg} from '../util/props.js'; 5 | 6 | testFunction('both', both, [futureArg, futureArg], assertValidFuture); 7 | 8 | test('resolves to a tuple of both resolution values', function (){ 9 | return Promise.all([ 10 | assertCrashed(both(crashed)(crashed), error), 11 | assertCrashed(both(rejected)(crashed), error), 12 | assertCrashed(both(crashed)(resolved), error), 13 | assertCrashed(both(resolved)(crashed), error), 14 | 15 | assertRejected(both(rejected)(rejected), 'rejected'), 16 | assertRejected(both(rejected)(rejectedSlow), 'rejected'), 17 | assertRejected(both(rejectedSlow)(rejected), 'rejected'), 18 | assertRejected(both(crashed)(rejected), 'rejected'), 19 | assertRejected(both(rejected)(crashedSlow), 'rejected'), 20 | assertRejected(both(resolved)(rejected), 'rejected'), 21 | assertRejected(both(rejected)(resolved), 'rejected'), 22 | 23 | assertResolved(both(resolved)(resolved), ['resolved', 'resolved']), 24 | assertResolved(both(resolved)(resolvedSlow), ['resolved', 'resolvedSlow']), 25 | assertResolved(both(resolvedSlow)(resolved), ['resolvedSlow', 'resolved']), 26 | assertResolved(both(resolvedSlow)(resolvedSlow), ['resolvedSlow', 'resolvedSlow']), 27 | ]); 28 | }); 29 | 30 | test('[GH #118] does not call the left computation twice', function (cb){ 31 | var called = false; 32 | var left = node(function (f){ called ? cb(error) : setTimeout(f, 20, null, called = true) }); 33 | return done(cb)(both(left)(resolvedSlow)); 34 | }); 35 | 36 | test('[GH #118] does not call the right computation twice', function (cb){ 37 | var called = false; 38 | var right = node(function (f){ called ? cb(error) : setTimeout(f, 20, null, called = true) }); 39 | return done(cb)(both(resolvedSlow)(right)); 40 | }); 41 | 42 | test('cancels the right if the left rejects', function (done){ 43 | var m = both(rejectedSlow)(Future(function (){ return function (){ return done() } })); 44 | m._interpret(done, noop, noop); 45 | }); 46 | 47 | test('cancels the left if the right rejects', function (done){ 48 | var m = both(Future(function (){ return function (){ return done() } }))(rejectedSlow); 49 | m._interpret(done, noop, noop); 50 | }); 51 | 52 | test('creates a cancel function which cancels both Futures', function (done){ 53 | var cancelled = false; 54 | var m = Future(function (){ return function (){ return (cancelled ? done() : (cancelled = true)) } }); 55 | var cancel = both(m)(m)._interpret(done, noop, noop); 56 | cancel(); 57 | }); 58 | 59 | test('displays correctly as string', function (){ 60 | eq(both(rejected)(resolved).toString(), 'both (reject ("rejected")) (resolve ("resolved"))'); 61 | }); 62 | -------------------------------------------------------------------------------- /test/unit/4.chain-rej.js: -------------------------------------------------------------------------------- 1 | import {chainRej, resolve, reject} from '../../index.js'; 2 | import {test, assertCrashed, assertRejected, assertResolved, assertValidFuture, bang, throwing, error, eq} from '../util/util.js'; 3 | import {rejected, resolved, rejectedSlow} from '../util/futures.js'; 4 | import {testFunction, functionArg, futureArg} from '../util/props.js'; 5 | 6 | testFunction('chainRej', chainRej, [functionArg, futureArg], assertValidFuture); 7 | 8 | test('crashes when the given function does not return Future', function (){ 9 | return assertCrashed(chainRej(bang)(rejected), new TypeError( 10 | 'chainRej expects the return value from the function it\'s given to be a valid Future.\n' + 11 | ' Actual: "rejected!" :: String\n' + 12 | ' When called with: "rejected"' 13 | )); 14 | }); 15 | 16 | test('calls the function with the rejection reason and sequences the returned Future', function (){ 17 | return Promise.all([ 18 | assertResolved(chainRej(resolve)(rejected), 'rejected'), 19 | assertResolved(chainRej(resolve)(rejectedSlow), 'rejectedSlow'), 20 | assertResolved(chainRej(reject)(resolved), 'resolved'), 21 | assertRejected(chainRej(reject)(rejected), 'rejected'), 22 | assertCrashed(chainRej(throwing)(rejected), error), 23 | ]); 24 | }); 25 | 26 | test('displays correctly as string', function (){ 27 | eq(chainRej(resolve)(resolved).toString(), 'chainRej (' + resolve.toString() + ') (resolve ("resolved"))'); 28 | }); 29 | -------------------------------------------------------------------------------- /test/unit/4.chain.js: -------------------------------------------------------------------------------- 1 | import Either from 'sanctuary-either'; 2 | import {chain, resolve, reject} from '../../index.js'; 3 | import {test, assertCrashed, assertRejected, assertResolved, assertValidFuture, bang, eq, throwing, error} from '../util/util.js'; 4 | import {rejected, resolved, resolvedSlow} from '../util/futures.js'; 5 | import {testFunction, functionArg, chainArg} from '../util/props.js'; 6 | 7 | testFunction('chain', chain, [functionArg, chainArg], assertValidFuture); 8 | 9 | test('dispatches to Fantasy Land chain', function (){ 10 | eq(chain(Either.Left)(Either.Right(42)), Either.Left(42)); 11 | }); 12 | 13 | test('crashes when the given function does not return Future', function (){ 14 | return assertCrashed(chain(bang)(resolved), new TypeError( 15 | 'chain expects the return value from the function it\'s given to be a valid Future.\n' + 16 | ' Actual: "resolved!" :: String\n' + 17 | ' When called with: "resolved"' 18 | )); 19 | }); 20 | 21 | test('calls the function with the resolution value and sequences the returned Future', function (){ 22 | return Promise.all([ 23 | assertRejected(chain(reject)(resolved), 'resolved'), 24 | assertRejected(chain(reject)(resolvedSlow), 'resolvedSlow'), 25 | assertResolved(chain(resolve)(resolved), 'resolved'), 26 | assertRejected(chain(resolve)(rejected), 'rejected'), 27 | assertCrashed(chain(throwing)(resolved), error), 28 | ]); 29 | }); 30 | 31 | test('displays correctly as string', function (){ 32 | eq(chain(resolve)(resolved).toString(), 'chain (' + resolve.toString() + ') (resolve ("resolved"))'); 33 | }); 34 | -------------------------------------------------------------------------------- /test/unit/4.coalesce.js: -------------------------------------------------------------------------------- 1 | import {coalesce} from '../../index.js'; 2 | import {test, assertCrashed, assertResolved, assertValidFuture, bang, B, throwing, error, eq} from '../util/util.js'; 3 | import {resolved, rejected} from '../util/futures.js'; 4 | import {testFunction, functionArg, futureArg} from '../util/props.js'; 5 | 6 | testFunction('coalesce', coalesce, [functionArg, functionArg, futureArg], assertValidFuture); 7 | 8 | test('joins the rejection and resolution values into the resolution branch', function (){ 9 | return Promise.all([ 10 | assertResolved(coalesce(bang)(B(bang)(bang))(rejected), 'rejected!'), 11 | assertResolved(coalesce(bang)(B(bang)(bang))(resolved), 'resolved!!'), 12 | assertCrashed(coalesce(throwing)(B)(rejected), error), 13 | assertCrashed(coalesce(B)(throwing)(resolved), error), 14 | ]); 15 | }); 16 | 17 | test('displays correctly as string', function (){ 18 | eq(coalesce(bang)(B)(resolved).toString(), 'coalesce (' + bang.toString() + ') (' + B.toString() + ') (resolve ("resolved"))'); 19 | }); 20 | -------------------------------------------------------------------------------- /test/unit/4.lastly.js: -------------------------------------------------------------------------------- 1 | import {lastly, resolve, reject, map} from '../../index.js'; 2 | import {test, assertRejected, assertResolved, assertValidFuture, noop, eq} from '../util/util.js'; 3 | import {rejected, rejectedSlow, resolved, resolvedSlow} from '../util/futures.js'; 4 | import {testFunction, futureArg} from '../util/props.js'; 5 | 6 | testFunction('lastly', lastly, [futureArg, futureArg], assertValidFuture); 7 | 8 | test('runs the second Future when the first resolves', function (done){ 9 | lastly(map(done)(resolve(null)))(resolve(1))._interpret(done, noop, noop); 10 | }); 11 | 12 | test('runs the second Future when the first rejects', function (done){ 13 | lastly(map(done)(resolve(null)))(reject(1))._interpret(done, noop, noop); 14 | }); 15 | 16 | test('resolves with the resolution value of the first', function (){ 17 | var actual = lastly(resolve(2))(resolve(1)); 18 | return assertResolved(actual, 1); 19 | }); 20 | 21 | test('rejects with the rejection reason of the first if the second resolves', function (){ 22 | var actual = lastly(resolve(2))(reject(1)); 23 | return assertRejected(actual, 1); 24 | }); 25 | 26 | test('always rejects with the rejection reason of the second', function (){ 27 | var actualResolved = lastly(reject(2))(resolve(1)); 28 | var actualRejected = lastly(reject(2))(reject(1)); 29 | return Promise.all([ 30 | assertRejected(actualResolved, 2), 31 | assertRejected(actualRejected, 2), 32 | ]); 33 | }); 34 | 35 | test('does nothing after being cancelled', function (done){ 36 | const fail = () => fail(done); 37 | lastly(resolved)(resolvedSlow)._interpret(done, fail, fail)(); 38 | lastly(resolvedSlow)(resolved)._interpret(done, fail, fail)(); 39 | lastly(rejected)(rejectedSlow)._interpret(done, fail, fail)(); 40 | lastly(rejectedSlow)(rejected)._interpret(done, fail, fail)(); 41 | setTimeout(done, 25); 42 | }); 43 | 44 | test('displays correctly as string', function (){ 45 | eq(lastly(rejected)(resolved).toString(), 'lastly (reject ("rejected")) (resolve ("resolved"))'); 46 | }); 47 | -------------------------------------------------------------------------------- /test/unit/4.map-rej.js: -------------------------------------------------------------------------------- 1 | import {mapRej} from '../../index.js'; 2 | import {test, assertCrashed, assertRejected, assertResolved, assertValidFuture, bang, throwing, error, eq} from '../util/util.js'; 3 | import {rejected, resolved, resolvedSlow, rejectedSlow} from '../util/futures.js'; 4 | import {testFunction, functionArg, futureArg} from '../util/props.js'; 5 | 6 | testFunction('mapRej', mapRej, [functionArg, futureArg], assertValidFuture); 7 | 8 | test('maps the rejection branch with the given function', function (){ 9 | return Promise.all([ 10 | assertRejected(mapRej(bang)(rejected), 'rejected!'), 11 | assertResolved(mapRej(bang)(resolved), 'resolved'), 12 | assertCrashed(mapRej(throwing)(rejected), error), 13 | ]); 14 | }); 15 | 16 | test('does not resolve after being cancelled', function (done){ 17 | const fail = () => done(error); 18 | mapRej(fail)(resolvedSlow)._interpret(done, fail, fail)(); 19 | setTimeout(done, 25); 20 | }); 21 | 22 | test('does not reject after being cancelled', function (done){ 23 | const fail = () => done(error); 24 | mapRej(fail)(rejectedSlow)._interpret(done, fail, fail)(); 25 | setTimeout(done, 25); 26 | }); 27 | 28 | test('displays correctly as string', function (){ 29 | eq(mapRej(bang)(rejected).toString(), 'mapRej (' + bang.toString() + ') (reject ("rejected"))'); 30 | }); 31 | -------------------------------------------------------------------------------- /test/unit/4.map.js: -------------------------------------------------------------------------------- 1 | import Either from 'sanctuary-either'; 2 | import {map} from '../../index.js'; 3 | import {test, assertCrashed, assertRejected, assertResolved, assertValidFuture, bang, eq, throwing, error} from '../util/util.js'; 4 | import {rejected, resolved, resolvedSlow, rejectedSlow} from '../util/futures.js'; 5 | import {testFunction, functionArg, functorArg} from '../util/props.js'; 6 | 7 | testFunction('map', map, [functionArg, functorArg], assertValidFuture); 8 | 9 | test('dispatches to Fantasy Land map', function (){ 10 | eq(map(bang)(Either.Right('hello')), Either.Right('hello!')); 11 | }); 12 | 13 | test('maps the resolution branch with the given function', function (){ 14 | return Promise.all([ 15 | assertRejected(map(bang)(rejected), 'rejected'), 16 | assertResolved(map(bang)(resolved), 'resolved!'), 17 | assertCrashed(map(throwing)(resolved), error), 18 | ]); 19 | }); 20 | 21 | test('does not resolve after being cancelled', function (done){ 22 | const fail = () => done(error); 23 | map(fail)(resolvedSlow)._interpret(done, fail, fail)(); 24 | setTimeout(done, 25); 25 | }); 26 | 27 | test('does not reject after being cancelled', function (done){ 28 | const fail = () => done(error); 29 | map(fail)(rejectedSlow)._interpret(done, fail, fail)(); 30 | setTimeout(done, 25); 31 | }); 32 | 33 | test('displays correctly as string', function (){ 34 | eq(map(bang)(resolved).toString(), 'map (' + bang.toString() + ') (resolve ("resolved"))'); 35 | }); 36 | -------------------------------------------------------------------------------- /test/unit/4.pap.js: -------------------------------------------------------------------------------- 1 | import {Future, resolve, reject, after} from '../../index.js'; 2 | import {pap} from '../../src/pap.js'; 3 | import {test, assertCrashed, assertRejected, assertResolved, assertValidFuture, noop, add} from '../util/util.js'; 4 | import {testFunction, futureArg} from '../util/props.js'; 5 | 6 | testFunction('pap', pap, [futureArg, futureArg], assertValidFuture); 7 | 8 | test('crashes when the other does not resolve to a Function', function (){ 9 | var m = pap(resolve(1))(resolve(null)); 10 | return assertCrashed(m, new TypeError( 11 | 'pap expects the second Future to resolve to a Function\n' + 12 | ' Actual: null' 13 | )); 14 | }); 15 | 16 | test('applies the Function on the right to the value on the left', function (){ 17 | return Promise.all([ 18 | assertResolved(pap(resolve(1))(resolve(add(1))), 2), 19 | assertRejected(pap(resolve(add(1)))(reject('err')), 'err'), 20 | assertRejected(pap(reject('err'))(resolve(add(1))), 'err'), 21 | assertResolved(pap(after(20)(1))(resolve(add(1))), 2), 22 | assertResolved(pap(resolve(1))(after(20)(add(1))), 2), 23 | ]); 24 | }); 25 | 26 | test('cancels the left Future if cancel is called while it is running', function (done){ 27 | var left = Future(function (){ return function (){ return done() } }); 28 | var right = resolve(add(1)); 29 | var cancel = pap(left)(right)._interpret(done, noop, noop); 30 | cancel(); 31 | }); 32 | 33 | test('cancels the right Future if cancel is called while it is running', function (done){ 34 | var left = resolve(1); 35 | var right = Future(function (){ return function (){ return done() } }); 36 | var cancel = pap(left)(right)._interpret(done, noop, noop); 37 | cancel(); 38 | }); 39 | -------------------------------------------------------------------------------- /test/unit/4.race.js: -------------------------------------------------------------------------------- 1 | import {Future, race} from '../../index.js'; 2 | import {test, assertCrashed, assertRejected, assertResolved, assertValidFuture, error, noop, eq} from '../util/util.js'; 3 | import {crashed, crashedSlow, rejected, rejectedSlow, resolved, resolvedSlow} from '../util/futures.js'; 4 | import {testFunction, futureArg} from '../util/props.js'; 5 | 6 | testFunction('race', race, [futureArg, futureArg], assertValidFuture); 7 | 8 | test('races one Future against another', function (){ 9 | return Promise.all([ 10 | assertCrashed(race(crashed)(resolvedSlow), error), 11 | assertResolved(race(crashedSlow)(resolved), 'resolved'), 12 | assertCrashed(race(crashed)(rejectedSlow), error), 13 | assertRejected(race(crashedSlow)(rejected), 'rejected'), 14 | assertResolved(race(resolved)(crashedSlow), 'resolved'), 15 | assertCrashed(race(resolvedSlow)(crashed), error), 16 | assertRejected(race(rejected)(crashedSlow), 'rejected'), 17 | assertCrashed(race(rejectedSlow)(crashed), error), 18 | assertResolved(race(resolved)(resolvedSlow), 'resolved'), 19 | assertResolved(race(resolvedSlow)(resolved), 'resolved'), 20 | assertRejected(race(rejectedSlow)(rejected), 'rejected'), 21 | assertRejected(race(rejected)(rejectedSlow), 'rejected'), 22 | assertResolved(race(rejectedSlow)(resolved), 'resolved'), 23 | assertRejected(race(rejected)(resolvedSlow), 'rejected'), 24 | assertResolved(race(resolved)(rejectedSlow), 'resolved'), 25 | assertRejected(race(resolvedSlow)(rejected), 'rejected'), 26 | ]); 27 | }); 28 | 29 | test('cancels the right if the left resolves', function (done){ 30 | var m = race(resolvedSlow)(Future(function (){ return function (){ return done() } })); 31 | m._interpret(done, noop, noop); 32 | }); 33 | 34 | test('cancels the left if the right resolves', function (done){ 35 | var m = race(Future(function (){ return function (){ return done() } }))(resolvedSlow); 36 | m._interpret(done, noop, noop); 37 | }); 38 | 39 | test('cancels the right if the left rejects', function (done){ 40 | var m = race(rejectedSlow)(Future(function (){ return function (){ return done() } })); 41 | m._interpret(done, noop, noop); 42 | }); 43 | 44 | test('cancels the left if the right rejects', function (done){ 45 | var m = race(Future(function (){ return function (){ return done() } }))(rejectedSlow); 46 | m._interpret(done, noop, noop); 47 | }); 48 | 49 | test('creates a cancel function which cancels both Futures', function (done){ 50 | var cancelled = false; 51 | var m = Future(function (){ return function (){ return (cancelled ? done() : (cancelled = true)) } }); 52 | var cancel = race(m)(m)._interpret(done, noop, noop); 53 | cancel(); 54 | }); 55 | 56 | test('displays correctly as string', function (){ 57 | eq(race(rejected)(resolved).toString(), 'race (reject ("rejected")) (resolve ("resolved"))'); 58 | }); 59 | -------------------------------------------------------------------------------- /test/unit/4.swap.js: -------------------------------------------------------------------------------- 1 | import {swap, resolve, reject} from '../../index.js'; 2 | import {test, assertRejected, assertResolved, assertValidFuture, eq} from '../util/util.js'; 3 | import {testFunction, futureArg} from '../util/props.js'; 4 | 5 | testFunction('swap', swap, [futureArg], assertValidFuture); 6 | 7 | test('rejects with the resolution value', function (){ 8 | var actual = swap(resolve(1)); 9 | return assertRejected(actual, 1); 10 | }); 11 | 12 | test('resolves with the rejection reason', function (){ 13 | var actual = swap(reject(1)); 14 | return assertResolved(actual, 1); 15 | }); 16 | 17 | test('displays correctly as string', function (){ 18 | eq(swap(resolve(42)).toString(), 'swap (resolve (42))'); 19 | }); 20 | -------------------------------------------------------------------------------- /test/unit/5.chain-rec.js: -------------------------------------------------------------------------------- 1 | import chai from 'chai'; 2 | import {resolve, after, reject} from '../../index.js'; 3 | import {chainRec} from '../../src/future.js'; 4 | import {isIteration} from '../../src/internal/iteration.js'; 5 | import {test, assertCrashed, assertRejected, assertResolved, error, noop} from '../util/util.js'; 6 | 7 | var expect = chai.expect; 8 | 9 | test('is a binary function', function (){ 10 | expect(chainRec).to.be.a('function'); 11 | expect(chainRec.length).to.equal(2); 12 | }); 13 | 14 | test('crashes if the iterator throws', function (){ 15 | var m = chainRec(function (){ throw error }); 16 | return assertCrashed(m, error); 17 | }); 18 | 19 | test('does not break if the iteration does not contain a value key', function (){ 20 | var actual = chainRec(function (f, g, x){ return (x, resolve({done: true})) }, 0); 21 | return assertResolved(actual, undefined); 22 | }); 23 | 24 | test('calls the function with Next, Done and the initial value', function (){ 25 | chainRec(function (next, done, x){ 26 | expect(next).to.be.a('function'); 27 | expect(next.length).to.equal(1); 28 | expect(next(x)).to.satisfy(isIteration); 29 | expect(done).to.be.a('function'); 30 | expect(done.length).to.equal(1); 31 | expect(done(x)).to.satisfy(isIteration); 32 | expect(x).to.equal(42); 33 | return resolve(done(x)); 34 | }, 42)._interpret(noop, noop, noop); 35 | }); 36 | 37 | test('calls the function with the value from the current iteration', function (){ 38 | var i = 0; 39 | chainRec(function (f, g, x){ 40 | expect(x).to.equal(i); 41 | return x < 5 ? resolve(f(++i)) : resolve(g(x)); 42 | }, i)._interpret(noop, noop, noop); 43 | }); 44 | 45 | test('works asynchronously', function (){ 46 | var actual = chainRec(function (f, g, x){ return after(10)(x < 5 ? f(x + 1) : g(x)) }, 0); 47 | return assertResolved(actual, 5); 48 | }); 49 | 50 | test('responds to failure', function (){ 51 | var m = chainRec(function (f, g, x){ return reject(x) }, 1); 52 | return assertRejected(m, 1); 53 | }); 54 | 55 | test('responds to failure after chaining async', function (){ 56 | var m = chainRec( 57 | function (f, g, x){ return x < 2 ? after(10)(f(x + 1)) : reject(x) }, 0 58 | ); 59 | return assertRejected(m, 2); 60 | }); 61 | 62 | test('can be cancelled straight away', function (done){ 63 | const fail = () => done(error); 64 | chainRec(function (f, g, x){ return after(10)(g(x)) }, 1) 65 | ._interpret(done, fail, fail)(); 66 | setTimeout(done, 20); 67 | }); 68 | 69 | test('can be cancelled after some iterations', function (done){ 70 | const fail = () => done(error); 71 | var m = chainRec(function (f, g, x){ return after(10)(x < 5 ? f(x + 1) : g(x)) }, 0); 72 | var cancel = m._interpret(done, fail, fail); 73 | setTimeout(cancel, 25); 74 | setTimeout(done, 70); 75 | }); 76 | -------------------------------------------------------------------------------- /test/unit/5.par.js: -------------------------------------------------------------------------------- 1 | import chai from 'chai'; 2 | import Z from 'sanctuary-type-classes'; 3 | import {Future, Par, seq, resolve, reject, never, ap, map, alt, and} from '../../index.js'; 4 | import {test, add, assertCrashed, assertRejected, assertResolved, bang, error, noop, throws} from '../util/util.js'; 5 | import {rejected, resolved, resolvedSlow} from '../util/futures.js'; 6 | 7 | var expect = chai.expect; 8 | 9 | var mf = resolve(bang); 10 | 11 | test('is a unary function', function (){ 12 | expect(Par).to.be.a('function'); 13 | expect(Par.length).to.equal(1); 14 | }); 15 | 16 | test('throws when not given a Future', function (){ 17 | var f = function (){ return Par(1) }; 18 | throws(f, new TypeError( 19 | 'Par() expects its first argument to be a valid Future.\n' + 20 | ' Actual: 1 :: Number' 21 | )); 22 | }); 23 | 24 | test('of resolves with the value', function (){ 25 | var m = Z.of(Par, 1); 26 | return assertResolved(seq(m), 1); 27 | }); 28 | 29 | test('zero creates a never-ending ConcurrentFuture', function (){ 30 | var m = Z.zero(Par); 31 | expect(seq(m)).to.equal(never); 32 | }); 33 | 34 | test('ap throws TypeError when the Future does not resolve to a Function', function (){ 35 | var m = seq(ap(Par(resolved))(Par(resolve(1)))); 36 | return assertCrashed(m, new TypeError( 37 | 'pap expects the second Future to resolve to a Function\n' + 38 | ' Actual: 1' 39 | )); 40 | }); 41 | 42 | test('ap calls the function contained in the given Future to its contained value', function (){ 43 | var actual = ap(Par(resolved))(Par(mf)); 44 | return assertResolved(seq(actual), 'resolved!'); 45 | }); 46 | 47 | test('ap rejects if one of the two reject', function (){ 48 | var left = ap(Par(rejected))(Par(mf)); 49 | var right = ap(Par(resolved))(Par(rejected)); 50 | return Promise.all([ 51 | assertRejected(seq(left), 'rejected'), 52 | assertRejected(seq(right), 'rejected'), 53 | ]); 54 | }); 55 | 56 | test('ap does not matter if either resolves late', function (){ 57 | var left = ap(Par(resolvedSlow))(Par(mf)); 58 | var right = ap(Par(resolved))(Par(and(mf)(resolvedSlow))); 59 | return Promise.all([ 60 | assertResolved(seq(left), 'resolvedSlow!'), 61 | assertResolved(seq(right), 'resolved!'), 62 | ]); 63 | }); 64 | 65 | test('ap cannot reject twice', function (){ 66 | var actual = ap(Par(rejected))(Par(rejected)); 67 | return assertRejected(seq(actual), 'rejected'); 68 | }); 69 | 70 | test('ap creates a cancel function which cancels both Futures', function (done){ 71 | var cancelled = false; 72 | var m = Par(Future(function (){ return function (){ return (cancelled ? done() : (cancelled = true)) } })); 73 | var cancel = seq(ap(m)(m))._interpret(done, noop, noop); 74 | cancel(); 75 | }); 76 | 77 | test('ap shows a reasonable representation when cast to string', function (){ 78 | var m = ap(Par(reject(0)))(Par(resolve(1))); 79 | var s = 'Par (pap (reject (0)) (resolve (1)))'; 80 | expect(m.toString()).to.equal(s); 81 | }); 82 | 83 | test('map applies the given function to its inner', function (){ 84 | var actual = map(add(1))(Par(resolve(1))); 85 | return assertResolved(seq(actual), 2); 86 | }); 87 | 88 | test('map does not map rejected state', function (){ 89 | var actual = map(function (){ return 'mapped' })(Par(rejected)); 90 | return assertRejected(seq(actual), 'rejected'); 91 | }); 92 | 93 | test('map shows a reasonable representation when cast to string', function (){ 94 | var m = map(noop)(Par(resolved)); 95 | var expected = 'Par (map (' + noop.toString() + ') (resolve ("resolved")))'; 96 | expect(m.toString()).to.equal(expected); 97 | }); 98 | 99 | test('alt rejects when the first one rejects', function (){ 100 | var m1 = Par(Future(function (rej, res){ setTimeout(res, 50, 1); return noop })); 101 | var m2 = Par(Future(function (rej){ setTimeout(rej, 5, error); return noop })); 102 | return assertRejected(seq(alt(m1)(m2)), error); 103 | }); 104 | 105 | test('alt resolves when the first one resolves', function (){ 106 | var m1 = Par(Future(function (rej, res){ setTimeout(res, 5, 1); return noop })); 107 | var m2 = Par(Future(function (rej){ setTimeout(rej, 50, error); return noop })); 108 | return assertResolved(seq(alt(m1)(m2)), 1); 109 | }); 110 | 111 | test('alt shows a reasonable representation when cast to string', function (){ 112 | var m = alt(Par(resolve(1)))(Par(resolve(2))); 113 | var s = 'Par (race (resolve (1)) (resolve (2)))'; 114 | expect(m.toString()).to.equal(s); 115 | }); 116 | -------------------------------------------------------------------------------- /test/unit/5.seq.js: -------------------------------------------------------------------------------- 1 | import {seq} from '../../index.js'; 2 | import {testFunction, parallelArg} from '../util/props.js'; 3 | import {assertValidFuture} from '../util/util.js'; 4 | 5 | testFunction('seq', seq, [parallelArg], assertValidFuture); 6 | -------------------------------------------------------------------------------- /test/util/futures.js: -------------------------------------------------------------------------------- 1 | import {Future, resolve, reject, after, rejectAfter, and} from '../../index.js'; 2 | import {crash} from '../../src/future.js'; 3 | import {error} from '../util/util.js'; 4 | 5 | export var mock = Object.create(Future.prototype); 6 | mock._interpret = function (){ throw new Error('Override _interpret on mock Future') }; 7 | mock.toString = function (){ return '(util.mock)' }; 8 | 9 | export var resolved = resolve('resolved'); 10 | export var rejected = reject('rejected'); 11 | export var resolvedSlow = after(20)('resolvedSlow'); 12 | export var rejectedSlow = rejectAfter(20)('rejectedSlow'); 13 | export var crashed = crash(error); 14 | export var crashedSlow = and(crashed)(after(20)(null)); 15 | -------------------------------------------------------------------------------- /test/util/props.js: -------------------------------------------------------------------------------- 1 | import type from 'sanctuary-type-identifiers'; 2 | import show from 'sanctuary-show'; 3 | import jsc from 'jsverify'; 4 | 5 | import {ordinal} from '../../src/internal/const.js'; 6 | import {eq, error, throws, test} from './util.js'; 7 | import { 8 | any, 9 | anyFuture, 10 | anyNonFuture, 11 | anyParallel, 12 | anyFunction, 13 | anyResolvedFuture, 14 | FutureArb, 15 | } from '../arbitraries.js'; 16 | 17 | export * from '../arbitraries.js'; 18 | 19 | export var array = jsc.array; 20 | export var nearray = jsc.nearray; 21 | export var bool = jsc.bool; 22 | export var constant = jsc.constant; 23 | export var falsy = jsc.falsy; 24 | export var fn = jsc.fn; 25 | export var letrec = jsc.letrec; 26 | export var nat = jsc.nat; 27 | export var number = jsc.number; 28 | export var oneof = jsc.oneof; 29 | export var string = jsc.string; 30 | export var elements = jsc.elements; 31 | export var suchthat = jsc.suchthat; 32 | 33 | export function _of (rarb){ 34 | return FutureArb(string, rarb); 35 | } 36 | 37 | export function property (name){ 38 | const args = Array.from(arguments).slice(1); 39 | test(name, () => { 40 | return jsc.assert(jsc.forall.apply(null, args)); 41 | }); 42 | } 43 | 44 | export function f (x){ 45 | return {f: x}; 46 | } 47 | 48 | export function g (x){ 49 | return {g: x}; 50 | } 51 | 52 | export var altArg = { 53 | name: 'have Alt implemented', 54 | valid: anyFuture, 55 | invalid: anyNonFuture, 56 | }; 57 | 58 | export var applyArg = { 59 | name: 'have Apply implemented', 60 | valid: anyFuture, 61 | invalid: anyNonFuture, 62 | }; 63 | 64 | export var bifunctorArg = { 65 | name: 'have Bifunctor implemented', 66 | valid: anyFuture, 67 | invalid: anyNonFuture, 68 | }; 69 | 70 | export var chainArg = { 71 | name: 'have Chain implemented', 72 | valid: anyFuture, 73 | invalid: anyNonFuture, 74 | }; 75 | 76 | export var functorArg = { 77 | name: 'have Functor implemented', 78 | valid: anyFuture, 79 | invalid: anyNonFuture, 80 | }; 81 | 82 | export var functionArg = { 83 | name: 'be a Function', 84 | valid: anyFunction, 85 | invalid: oneof(number, string, bool, falsy, constant(error)), 86 | }; 87 | 88 | export var futureArg = { 89 | name: 'be a valid Future', 90 | valid: anyFuture, 91 | invalid: anyNonFuture, 92 | }; 93 | 94 | export var resolvedFutureArg = { 95 | name: 'be a valid Future', 96 | valid: anyResolvedFuture, 97 | invalid: anyNonFuture, 98 | }; 99 | 100 | export var positiveIntegerArg = { 101 | name: 'be a positive Integer', 102 | valid: suchthat(nat, function (x){ return x > 0 }), 103 | invalid: oneof(bool, constant(0.5)), 104 | }; 105 | 106 | export var futureArrayArg = { 107 | name: 'be an Array of valid Futures', 108 | valid: array(anyFuture), 109 | invalid: oneof(nearray(anyNonFuture), any), 110 | }; 111 | 112 | export var parallelArg = { 113 | name: 'be a ConcurrentFuture', 114 | valid: anyParallel, 115 | invalid: any, 116 | }; 117 | 118 | export var anyArg = { 119 | name: 'be anything', 120 | valid: any, 121 | invalid: null, 122 | }; 123 | 124 | var getValid = function (x){ return x.valid }; 125 | var generateValid = function (x){ return getValid(x).generator(1) }; 126 | 127 | var capply = function (f, args){ 128 | return args.reduce(function (g, x){ return g(x) }, f); 129 | }; 130 | 131 | export function testFunction (name, func, args, assert){ 132 | var validArbs = args.map(getValid); 133 | var validArgs = args.map(generateValid); 134 | 135 | test('is a curried ' + args.length + '-ary function', function (){ 136 | eq(typeof func, 'function'); 137 | eq(func.length, 1); 138 | validArgs.slice(0, -1).forEach(function (_, idx){ 139 | var partial = capply(func, validArgs.slice(0, idx + 1)); 140 | eq(typeof partial, 'function'); 141 | eq(partial.length, 1); 142 | }); 143 | }); 144 | 145 | args.forEach(function (arg, idx){ 146 | var priorArgs = args.slice(0, idx); 147 | var followingArgs = args.slice(idx + 1); 148 | var validPriorArgs = priorArgs.map(generateValid); 149 | var validFollowingArgs = followingArgs.map(generateValid); 150 | if(arg !== anyArg){ 151 | property('throws when the ' + ordinal[idx] + ' argument is invalid', arg.invalid, function (value){ 152 | throws(function (){ 153 | capply(func, validPriorArgs.concat([value]).concat(validFollowingArgs)); 154 | }, new TypeError( 155 | name + '() expects its ' + ordinal[idx] + ' argument to ' + arg.name + '.\n' + 156 | ' Actual: ' + show(value) + ' :: ' + type.parse(type(value)).name 157 | )); 158 | return true; 159 | }); 160 | property('throws when the ' + ordinal[idx] + ' invocation has more than one argument', arg.valid, function (value){ 161 | throws(function (){ 162 | var partial = capply(func, validPriorArgs); 163 | partial(value, 42); 164 | }, new TypeError( 165 | name + '() expects to be called with a single argument per invocation\n' + 166 | ' Saw: 2 arguments\n' + 167 | ' First: ' + show(value) + ' :: ' + type.parse(type(value)).name + '\n' + 168 | ' Second: 42 :: Number' 169 | )); 170 | return true; 171 | }); 172 | } 173 | }); 174 | 175 | property.apply(null, ['returns valid output when given valid input'].concat(validArbs).concat([function (){ 176 | return assert(capply(func, Array.from(arguments))); 177 | }])); 178 | } 179 | -------------------------------------------------------------------------------- /test/util/util.js: -------------------------------------------------------------------------------- 1 | import oletus from 'oletus'; 2 | import show from 'sanctuary-show'; 3 | import {reject, resolve} from '../../index.js'; 4 | import {crash} from '../../src/future.js'; 5 | import * as assert from '../assertions.js'; 6 | export * from '../../src/internal/predicates.js'; 7 | 8 | export var STACKSIZE = (function r (){ try{ return 1 + r() }catch(e){ return 1 } }()); 9 | export var noop = function (){}; 10 | export var add = function (a){ return function (b){ return a + b } }; 11 | export var sub = function (a){ return function (b){ return a - b } }; 12 | export var bang = function (s){ return (s + '!') }; 13 | export var I = function (x){ return x }; 14 | export var B = function (f){ return function (g){ return function (x){ return f(g(x)) } } }; 15 | export var K = function (x){ return function (){ return x } }; 16 | export var T = function (x){ return function (f){ return f(x) } }; 17 | export var error = new Error('Intentional error for unit testing'); 18 | export var throwing = function (){ throw error }; 19 | 20 | export function test (name, impl){ 21 | oletus(name, () => ( 22 | impl.length === 0 ? impl() : new Promise((res, rej) => { impl(e => e ? rej(e) : res()) }) 23 | )); 24 | } 25 | 26 | export var eq = function eq (actual, expected){ 27 | assert.equality(actual)(expected); 28 | }; 29 | 30 | export var throws = function throws (f, expected){ 31 | try{ 32 | f(); 33 | }catch(actual){ 34 | eq(typeof actual, typeof expected); 35 | eq(actual.constructor, expected.constructor); 36 | eq(actual.name, expected.name); 37 | eq(actual.message, expected.message); 38 | return; 39 | } 40 | throw new Error('Expected the function to throw'); 41 | }; 42 | 43 | export var itRaises = function itRaises (when, f, e){ 44 | test('raises ' + when, function (done){ 45 | var listeners = process.rawListeners('uncaughtException'); 46 | process.removeAllListeners('uncaughtException'); 47 | process.once('uncaughtException', function (actual){ 48 | listeners.forEach(function (f){ process.on('uncaughtException', f) }); 49 | try { 50 | eq(actual.message, e.message); 51 | }catch(err){ 52 | done(err); 53 | return; 54 | } 55 | done(); 56 | }); 57 | f(); 58 | }); 59 | }; 60 | 61 | export var isDeepStrictEqual = function isDeepStrictEqual (actual, expected){ 62 | try{ 63 | eq(actual, expected); 64 | return true; 65 | }catch(e){ 66 | return false; 67 | } 68 | }; 69 | 70 | export var repeat = function (n, x){ 71 | var out = new Array(n); 72 | while(n-- > 0){ out[n] = x } //eslint-disable-line 73 | return out; 74 | }; 75 | 76 | export var promiseTimeout = function (t, p){ 77 | return Promise.race([ 78 | p, 79 | new Promise((res, rej) => { 80 | setTimeout(rej, t, new Error(`Timeout of ${t}ms reached`)); 81 | }), 82 | ]); 83 | }; 84 | 85 | export var assertIsFuture = function (x){ 86 | return assert.future(x); 87 | }; 88 | 89 | export var assertValidFuture = function (x){ 90 | assertIsFuture(x); 91 | 92 | eq(typeof x.extractLeft, 'function'); 93 | eq(x.extractLeft.length, 0); 94 | eq(Array.isArray(x.extractLeft()), true); 95 | 96 | eq(typeof x.extractRight, 'function'); 97 | eq(x.extractRight.length, 0); 98 | eq(Array.isArray(x.extractRight()), true); 99 | 100 | eq(typeof x._transform, 'function'); 101 | eq(x._transform.length, 1); 102 | 103 | eq(typeof x._interpret, 'function'); 104 | eq(typeof x._interpret(noop, noop, noop), 'function'); 105 | eq(x._interpret(noop, noop, noop).length, 0); 106 | eq(x._interpret(noop, noop, noop)(), undefined); 107 | 108 | return true; 109 | }; 110 | 111 | export var assertEqual = function (a, b){ 112 | return assert.equivalence(a)(b); 113 | }; 114 | 115 | var assertEqualByErrorMessage = assert.makeEquivalence(a => b => { 116 | return assert.equality(a.message)(b.message); 117 | }); 118 | 119 | export var assertCrashed = function (m, x){ 120 | return assertEqualByErrorMessage(m)(crash(x)); 121 | }; 122 | 123 | export var assertRejected = function (m, x){ 124 | return assertEqual(m, reject(x)); 125 | }; 126 | 127 | export var assertResolved = function (m, x){ 128 | return assertEqual(m, resolve(x)); 129 | }; 130 | 131 | export var onceOrError = function (f){ 132 | var called = false; 133 | return function (){ 134 | if(called){ throw new Error('Function ' + show(f) + ' was called twice') } 135 | called = true; 136 | return f.apply(null, arguments); 137 | }; 138 | }; 139 | 140 | export function assertStackTrace (name, x){ 141 | eq(typeof x, 'string'); 142 | eq(x.slice(0, name.length), name); 143 | var lines = x.slice(name.length).split('\n'); 144 | eq(lines.length > 0, true); 145 | } 146 | --------------------------------------------------------------------------------