Repository: RationalJS/future Branch: master Commit: b0e40ee53d0b Files: 15 Total size: 31.5 KB Directory structure: gitextract_80oxo1ot/ ├── .gitignore ├── .npmignore ├── .travis.yml ├── .vscode/ │ └── tasks.json ├── README.md ├── __tests__/ │ ├── TestFuture.res │ └── TestFutureJs.res ├── jest.config.js ├── package.json ├── rescript.json └── src/ ├── Future.res ├── Future.resi ├── FutureJs.res ├── FutureJs.resi └── FutureResult.res ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .DS_Store .merlin .bsb.lock npm-debug.log /lib/bs/ /node_modules/ package-lock.json *.bs.js .nyc_output/ coverage/ ================================================ FILE: .npmignore ================================================ .DS_Store .merlin .bsb.lock npm-debug.log /lib/bs/ /node_modules/ tests/ package-lock.json .vscode/ ================================================ FILE: .travis.yml ================================================ language: node_js node_js: - lts/* cache: yarn ================================================ FILE: .vscode/tasks.json ================================================ { "version": "2.0.0", "command": "npm", "options": { "cwd": "${workspaceRoot}" }, "type": "shell", "args": ["run", "start"], "presentation": { "echo": true, "reveal": "always", "focus": false, "panel": "shared" }, "isBackground": true, "problemMatcher": { "fileLocation": "absolute", "owner": "ocaml", "background": { "activeOnStart": false, "beginsPattern": ">>>> Start compiling", "endsPattern": ">>>> Finish compiling" }, "pattern": [ { "regexp": "^File \"(.*)\", line (\\d+)(?:, characters (\\d+)-(\\d+))?:$", "file": 1, "line": 2, "column": 3, "endColumn": 4 }, { "regexp": "^(?:(?:Parse\\s+)?(Warning|[Ee]rror)(?:\\s+\\d+)?:)?\\s+(.*)$", "severity": 1, "message": 2, "loop": true } ] }, "tasks": [ { "label": "npm", "type": "shell", "command": "npm", "args": [ "run", "start" ], "isBackground": true, "problemMatcher": { "fileLocation": "absolute", "owner": "ocaml", "background": { "activeOnStart": false, "beginsPattern": ">>>> Start compiling", "endsPattern": ">>>> Finish compiling" }, "pattern": [ { "regexp": "^File \"(.*)\", line (\\d+)(?:, characters (\\d+)-(\\d+))?:$", "file": 1, "line": 2, "column": 3, "endColumn": 4 }, { "regexp": "^(?:(?:Parse\\s+)?(Warning|[Ee]rror)(?:\\s+\\d+)?:)?\\s+(.*)$", "severity": 1, "message": 2, "loop": true } ] }, "group": { "_id": "build", "isDefault": false } } ] } ================================================ FILE: README.md ================================================ [![npm](https://img.shields.io/npm/v/reason-future.svg)](https://www.npmjs.com/package/reason-future) [![Build Status](https://travis-ci.org/RationalJS/future.svg?branch=master)](https://travis-ci.org/RationalJS/future) [![Coverage Status](https://coveralls.io/repos/github/RationalJS/future/badge.svg?branch=test-coverage)](https://coveralls.io/github/RationalJS/future?branch=test-coverage) # The Future is Now Future is a lightweight, functional alternative to `Js.Promise` for ReScript, designed to make async code more composable and maintainable. ### Compatibility - Use `reason-future@2.6.0` for ReasonML or ReScript ≤ v10. - Use `reason-future@3.0.0` for ReScript ≥ v11. ### Key Benefits of Using Future Over Promises: - **Lighter weight** – Only ~25 lines of implementation. - **Simpler** – Futures only resolve to a single type (as opposed to resolve and reject types), giving you more flexibility on your error handling. - **More robust** – Futures have sound typing (unlike JS promises). ## Installation First make sure you have rescript `>= 11.1.X`. Then install with npm: ``` $ npm install --save reason-future ``` Then add `"reason-future"` to your `rescript.json` dev dependencies: ``` { ... "bs-dependencies": [ "reason-future" ] } ``` ## Basic Usage To create a task, use `Future.make`. It provides a single `resolve` function, similar to how Promises work but without a `reject`: ```js let futureGreeting = Future.make(resolve => resolve("hi")); ``` To get the value of a future, use `Future.get`: ```js let futureGreeting = Future.make(resolve => resolve("hi")); futureGreeting->Future.get(x => Js.log("Got value: " ++ x)); /* Alternatively: */ Future.make(resolve => resolve("hi")) ->Future.get(x => Js.log("Got value: " ++ x)); ``` `Future.get` only *retrieves* the future value. If you want to **transform** it to a *different* value, then you should use `Future.map`: ```js /* Shortcut for: let future_A = Future.make(resolve => resolve(99)); */ let future_A = Future.value(99); let future_B = future_A->Future.map(n => n + 1); future_A->Future.get(n => Js.log(n)); /* logs: 99 */ future_B->Future.get(n => Js.log(n)); /* logs: 100 */ ``` And finally, if you `map` a future and return **another** future, you probably want to `flatMap` instead: ```js let futureNum = Future.value(50); let ft_a = futureNum->Future.map(n => Future.value(n + 10)); let ft_b = futureNum->Future.flatMap(n => Future.value(n + 20)); /* ft_a has type future(future(int)) – probably not what you want. */ /* ft_b has type future(int) */ ``` ## API Core functions. **Note:** `_` represents the future itself as inserted by `->` (the [pipe](https://rescript-lang.org/docs/manual/latest/pipe) operator). - `Future.make(resolver)` - Create a new, potentially-async future. - `Future.value(x)` - Create a new future with a plain value (synchronous). - `Future.map(_,fn)` - Transform a future value into another value - `Future.flatMap(_,fn)` - Transform a future value into another future value - `Future.get(_,fn)` - Get the value of a future - `Future.tap(_,fn)` - Do something with the value of a future without changing it. Returns the same future so you can continue using it in a pipeline. Convenient for side effects such as console logging. - `Future.all(_,fn)` - Turn a list of futures into a future of a list. Used when you want to wait for a collection of futures to complete before doing something (equivalent to Promise.all in Javascript). ### Result Convenience functions when working with a future `Result`. **Note:** `_` represents the future itself as inserted by `->` (the [pipe](https://rescript-lang.org/docs/manual/latest/pipe) operator). **Note 2**: The terms `Result.Ok` and `Result.Error` in this context are expected to be read as `Ok` and `Error`. - `Future.mapOk(_,fn)` - Transform a future value into another value, but only if the value is an `Result.Ok`. Similar to `Promise.prototype.then` - `Future.mapError(_,fn)` - Transform a future value into another value, but only if the value is an `Result.Error`. Similar to `Promise.prototype.catch` - `Future.tapOk(_,fn)` - Do something with the value of a future without changing it, but only if the value is a `Ok`. Returns the same future. Convenience for side effects such as console logging. - `Future.tapError(_,fn)` - Same as `tapOk` but for `Result.Error` The following are more situational: - `Future.flatMapOk(_, fn)` - Transform a future `Result.Ok` into a future `Result`. Flattens the inner future. - `Future.flatMapError(_, fn)` - Transform a future `Result.Error` into a future `Result`. Flattens the inner future. ### FutureJs Convenience functions for interop with JavaScript land. - `FutureJs.fromPromise(promise, errorTransformer)` - `promise` is the `RescriptCore.Promise.t('a)` that will be transformed into a `Future.t(result('a, 'e))` - `errorTransformer` allows you to determine how `Promise.error` objects will be transformed before they are returned wrapped within a `Error`. This allows you to implement the error handling method which best meets your application's needs. - `FutureJs.toPromise(future)` - `future` is any `Future.t('a)` which is transformed into a `RescriptCore.Promise.t('a)`. Always resolves, never rejects the promise. - `FutureJs.resultToPromise(future)` - `future` is the `Future.t(result('a, 'e))` which is transformed into a `RescriptCore.Promise.t('a)`. Resolves the promise on Ok result and rejects on Error result. Example use: ```js /* This error handler is super simple; you will probably want to write something more sophisticated in your app. */ let handleError = Js.String.make; somePromiseGetter() ->FutureJs.fromPromise(handleError) ->Future.tap(value => Js.log2("It worked!", value)) ->Future.tapError(err => Js.log2("uh on", err)); ``` See [Composible Error Handling in OCaml][error-handling] for several strategies that you may employ. ### Stack Safety By default this library is not stack safe, you will recieve a 'Maximum call stack size exceeded' error if you recurse too deeply. You can opt into stack safety by passing an optional parameter to the constructors of trampoline. This has a slight overhead. For example: ```reason let stackSafe = Future.make(~executor=`trampoline, resolver); let stackSafeValue = Future.value(~executor=`trampoline, "Value"); ``` ## TODO - [ ] Implement cancellation tokens - [x] Interop with `Js.Promise` - [x] `flatMapOk` / `flatMapError` (with [composable error handling](http://keleshev.com/composable-error-handling-in-ocaml)) ## Build ``` npm run build ``` ## Build + Watch ``` npm run start ``` ## Test ``` npm test ``` ## Editor If you use `vscode`, Press `Windows + Shift + B` it will build automatically [error-handling]: http://keleshev.com/composable-error-handling-in-ocaml ================================================ FILE: __tests__/TestFuture.res ================================================ open Jest open Expect describe("Future", () => { testAsync("sync chaining", finish => Future.value("one") ->Future.map(s => s ++ "!") ->Future.get(s => s->expect->toEqual("one!")->finish) ) testAsync("async chaining", finish => Future.delay(25, () => 20) ->Future.map(s => string_of_int(s)) ->Future.map(s => s ++ "!") ->Future.get(s => s->expect->toEqual("20!")->finish) ) testAsync("tap", finish => { let v = ref(0) Future.value(99) ->Future.tap(n => v := n + 1) ->Future.map(n => n - 9) ->Future.get(n => (n, v.contents)->expect->toEqual((90, 100))->finish) }) testAsync("flatMap", finish => Future.value(59) ->Future.flatMap(n => Future.value(n + 1)) ->Future.get(n => n->expect->toEqual(60)->finish) ) testAsync("map2", finish => Future.map2(Future.delay(20, () => 1), Future.value("a"), (num, str) => (num, str))->Future.get( tup => tup->expect->toEqual((1, "a"))->finish, ) ) testAsync("map5", finish => Future.map5( Future.delay(20, () => 0), Future.delay(40, () => 1.2), Future.delay(60, () => "foo"), Future.delay(80, () => list{}), Future.delay(100, () => ()), (a, b, c, d, e) => (a, b, c, d, e), )->Future.get(tup => tup->expect->toEqual((0, 1.2, "foo", list{}, ()))->finish) ) test("multiple gets", () => { let count = ref(0) let future = Future.make( resolve => { count := count.contents + 1 resolve(count.contents) }, ) future->Future.get(_ => ()) future->Future.get(_ => ()) count.contents->expect->toBe(1) }) testAsync("multiple gets (async)", finish => { let count = ref(0) let future = Future.sleep(25)->Future.map(() => 0)->Future.map(_ => count := count.contents + 1) future->Future.get(_ => ()) let initialCount = count.contents future->Future.get(_ => ()) future->Future.get(_ => (initialCount, count.contents)->expect->toEqual((0, 1))->finish) }) testAsync("all (async)", finish => { open Future all(list{value(1), delay(25, () => 2), delay(50, () => 3), sleep(75)->map(() => 4)}) }->Future.get( result => switch result { | list{1, 2, 3, 4} => pass->finish | _ => fail("Expected [1, 2, 3, 4]")->finish }, ) ) }) describe("Future Result", () => { let (\">>=", \"<$>") = { open Future (\">>=", \"<$>") } testAsync("mapOk 1", finish => Ok("two") ->Future.value ->Future.mapOk(s => s ++ "!") ->Future.get(r => Result.getExn(r)->expect->toEqual("two!")->finish) ) testAsync("mapOk 2", finish => Error("err2") ->Future.value ->Future.mapOk(s => s ++ "!") ->Future.get( r => switch r { | Ok(_) => fail("shouldn't be possible")->finish | Error(e) => e->expect->toEqual("err2")->finish }, ) ) testAsync("mapError 1", finish => Ok("three") ->Future.value ->Future.mapError(s => s ++ "!") ->Future.get(r => Result.getExn(r)->expect->toEqual("three")->finish) ) testAsync("mapError 2", finish => Error("err3") ->Future.value ->Future.mapError(s => s ++ "!") ->Future.get( r => switch r { | Ok(_) => fail("shouldn't be possible")->finish | Error(e) => e->expect->toEqual("err3!")->finish }, ) ) testAsync("flatMapOk 1", finish => Ok("four") ->Future.value ->Future.flatMapOk(s => Ok(s ++ "!")->Future.value) ->Future.get(r => Result.getExn(r)->expect->toEqual("four!")->finish) ) testAsync("flatMapOk 2", finish => Error("err4.1") ->Future.value ->Future.flatMapOk(s => Ok(s ++ "!")->Future.value) ->Future.get( r => switch r { | Ok(_) => fail("shouldn't be possible")->finish | Error(e) => e->expect->toEqual("err4.1")->finish }, ) ) testAsync("flatMapOk 3", finish => Error("err4") ->Future.value ->Future.flatMapError(e => Error(e ++ "!")->Future.value) ->Future.get( r => switch r { | Ok(_) => fail("shouldn't be possible")->finish | Error(e) => e->expect->toEqual("err4!")->finish }, ) ) testAsync("flatMapOkPure 1", finish => Ok("four") ->Future.value ->Future.flatMapOkPure(s => Ok(s ++ "!")) ->Future.get(r => Result.getExn(r)->expect->toEqual("four!")->finish) ) testAsync("flatMapOkPure 2", finish => Error("err4.1") ->Future.value ->Future.flatMapOkPure(s => Ok(s ++ "!")) ->Future.get( r => switch r { | Ok(_) => fail("shouldn't be possible")->finish | Error(e) => e->expect->toEqual("err4.1")->finish }, ) ) testAsync("flatMapError 1", finish => Ok("five") ->Future.value ->Future.flatMapError(s => Error(s ++ "!")->Future.value) ->Future.get(r => Result.getExn(r)->expect->toEqual("five")->finish) ) testAsync("flatMapError 2", finish => Error("err5") ->Future.value ->Future.flatMapError(e => Error(e ++ "!")->Future.value) ->Future.get( r => switch r { | Ok(_) => fail("shouldn't be possible")->finish | Error(e) => e->expect->toEqual("err5!")->finish }, ) ) testAsync("mapOk5 success", finish => Future.mapOk5( Future.value(Ok(0)), Future.value(Ok(1.1)), Future.value(Ok("")), Future.value(Ok(list{})), Future.value(Ok(Some("x"))), (a, b, c, d, e) => (a, b, c, d, e), )->Future.get( r => switch r { | Ok(tup) => tup->expect->toEqual((0, 1.1, "", list{}, Some("x")))->finish | Error(_) => fail("shouldn't be possible")->finish }, ) ) testAsync("mapOk5 fails on first error", finish => Future.mapOk5( Future.value(Ok(0)), Future.delay(20, () => Ok(1.)), Future.delay(10, () => Error("first")), Future.value(Ok("")), Future.delay(100, () => Error("second")), (_, _, _, _, _) => None, )->Future.get( r => switch r { | Ok(_) => fail("shouldn't be possible")->finish | Error(x) => x->expect->toEqual("first")->finish }, ) ) testAsync("tapOk 1", finish => { let x = ref(1) Ok(10) ->Future.value ->Future.tapOk(n => x := x.contents + n) ->Future.get(_ => x.contents->expect->toEqual(11)->finish) }) testAsync("tapOk 2", finish => { let y = ref(1) Error(10) ->Future.value ->Future.tapOk(n => y := y.contents + n) ->Future.get(_ => y.contents->expect->toEqual(1)->finish) }) testAsync("tapError 1", finish => { let x = ref(1) Ok(10) ->Future.value ->Future.tapError(n => x := x.contents + n) ->Future.get(_ => x.contents->expect->toEqual(1)->finish) }) testAsync("tapError", finish => { let y = ref(1) Error(10) ->Future.value ->Future.tapError(n => y := y.contents + n) ->Future.get(_ => y.contents->expect->toEqual(11)->finish) }) testAsync("<$> 1", finish => \"<$>"( \"<$>"(Future.value(Ok("infix ops")), x => x ++ " "), x => x ++ "rock!", )->Future.get(r => r->Result.getExn->expect->toEqual("infix ops rock!")->finish) ) testAsync("<$> 2", finish => \"<$>"( \"<$>"(Future.value(Error("infix ops")), x => x ++ " "), x => x ++ "suck!", )->Future.get( r => switch r { | Ok(_) => fail("shouldn't be possible")->finish | Error(e) => e->expect->toEqual("infix ops")->finish }, ) ) testAsync(">>= 1", finish => { let appendToString = (appendedString, s) => (s ++ appendedString)->Ok->Future.value \">>="( Future.value(Ok("infix ops")), s => appendToString(" still rock!", s), )->Future.get(r => r->expect->toEqual(Ok("infix ops still rock!"))->finish) }) testAsync("value recursion is stack safe", finish => { let next = x => Future.value(~executor=#trampoline, x + 1) let numberOfLoops = 10000 let rec loop = x => next(x) ->Future.flatMap(x => Future.value(x + 1)) ->Future.map(x => x + 1) ->Future.flatMap(x => Future.value(x + 1)->Future.flatMap(x => Future.value(x + 1))) ->Future.flatMap( x' => if x' > numberOfLoops { Future.value(x') } else { loop(x') }, ) loop(0)->Future.get(r => r->expect->toBeGreaterThan(numberOfLoops)->finish) }) testAsync("async recursion is stack safe", finish => { let next = x => Future.delay(~executor=#trampoline, 1, () => x + 1) let numberOfLoops = 1000 let rec loop = x => next(x)->Future.flatMap( x' => if x' == numberOfLoops { Future.value(x') } else { loop(x') }, ) loop(0)->Future.get(r => r->expect->toEqual(numberOfLoops)->finish) }) }) ================================================ FILE: __tests__/TestFutureJs.res ================================================ open Jest open Expect exception TestError(string) describe("FutureJs", () => { let errorTransformer = x => x testAsync("fromPromise (resolved)", finish => Promise.resolve(42) ->FutureJs.fromPromise(errorTransformer) ->Future.get(r => Result.getExn(r)->expect->toEqual(42)->finish) ) testAsync("fromPromise (rejected)", finish => { let err = TestError("oops!") Promise.reject(err) ->FutureJs.fromPromise(errorTransformer) ->Future.get( r => switch r { | Ok(_) => fail("shouldn't be possible")->finish | Error(_) => pass->finish }, ) }) testAsync("fromPromise (internal rejection)", finish => { let err = TestError("boom!") let promise = Promise.make((_, reject) => reject(err)) FutureJs.fromPromise(promise, errorTransformer)->Future.get( r => switch r { | Ok(_) => fail("shouldn't be possible")->finish | Error(_) => pass->finish }, ) }) testAsync("fromPromise (internal exception)", finish => { let err = TestError("explode!") let promise = Promise.make((_, _) => raise(err)) FutureJs.fromPromise(promise, errorTransformer)->Future.get( r => switch r { | Ok(_) => fail("shouldn't be possible")->finish | Error(_) => pass->finish }, ) }) testAsync("fromPromise (wrapped callback)", finish => { let err = TestError("throwback!") let nodeFn = callback => callback(err) let promise = Promise.make((_, reject) => nodeFn(err => reject(err))) FutureJs.fromPromise(promise, errorTransformer)->Future.get( r => switch r { | Ok(_) => fail("shouldn't be possible")->finish | Error(_) => pass->finish }, ) }) testAsync("fromPromise (layered failure)", finish => { let err = TestError("confused!") let nodeFn = callback => callback(err) let promise = Promise.make((_, reject) => nodeFn(err => reject(err))) let future = () => FutureJs.fromPromise(promise, errorTransformer) Future.value(Ok("ignored")) ->Future.flatMapOk(_ => future()) ->Future.get( r => switch r { | Ok(_) => fail("shouldn't be possible")->finish | Error(_) => pass->finish }, ) }) testPromise("toPromise", () => Future.delay(5, () => "payload") ->FutureJs.toPromise ->Promise.then(x => Promise.resolve(x->expect->toEqual("payload"))) ->Promise.catch(_ => raise(TestError("shouldn't be possible"))) ) testPromise("resultToPromise (Ok result)", () => FutureJs.resultToPromise(Future.delay(5, () => Ok("payload"))) ->Promise.then(x => Promise.resolve(x->expect->toEqual("payload"))) ->Promise.catch(_ => raise(TestError("shouldn't be possible"))) ) testPromise("resultToPromise (Error exn)", () => { let err = TestError("error!") FutureJs.resultToPromise(Future.delay(5, () => Error(err))) ->Promise.then(_ => raise(TestError("shouldn't be possible"))) ->Promise.catch(_ => Promise.resolve(pass)) // Going from Future to Promise loses information... }) testPromise("resultToPromise (Error `PolymorphicVariant)", () => { let err = #TestError FutureJs.resultToPromise(Future.delay(5, () => Error(err))) ->Promise.then(_ => raise(TestError("shouldn't be possible"))) ->Promise.catch(_ => Promise.resolve(pass)) // Going from Future to Promise loses information... }) }) ================================================ FILE: jest.config.js ================================================ module.exports = { testPathIgnorePatterns: ["/node_modules/", "__tests__/TestUtil.bs.js"], coveragePathIgnorePatterns: ["/node_modules/", "__tests__/TestUtil.bs.js*"] }; ================================================ FILE: package.json ================================================ { "name": "reason-future", "version": "3.0.0", "scripts": { "build": "rescript build -with-deps", "start": "rescript build -with-deps -w", "clean": "rescript clean -with-deps", "test": "yarn clean ; yarn build; jest --coverage --coverageReporters=text-lcov | coveralls", "test:only": "yarn jest" }, "files": [ "src/Future.res", "src/FutureResult.res", "src/FutureJs.res", "rescript.json", "README.md" ], "keywords": [ "Rescript", "Future", "Promise", "JavaScript", "Async" ], "author": "", "license": "MIT", "devDependencies": { "@glennsl/rescript-jest": "0.11.0", "coveralls": "3.1.0", "rescript": "11.1.4" }, "description": "A lightweight, functional alternative to Js.Promise for ReScript, designed for better composability and error handling.", "main": "index.js", "dependencies": { "@rescript/core": "^1.6.1" }, "peerDependencies": { "rescript": "^11.1.4" }, "repository": { "type": "git", "url": "git+https://github.com/rationaljs/future.git" }, "bugs": { "url": "https://github.com/rationaljs/future/issues" }, "homepage": "https://github.com/rationaljs/future#readme" } ================================================ FILE: rescript.json ================================================ { "name": "reason-future", "version": "3.0.0", "sources": [ { "dir": "src", "subdirs": true }, { "dir": "__tests__", "type": "dev" } ], "package-specs": { "module": "commonjs", "in-source": true }, "suffix": ".bs.js", "bs-dependencies": ["@rescript/core"], "bs-dev-dependencies": ["@glennsl/rescript-jest"], "bsc-flags": ["-open RescriptCore"], "warnings": { "error": "+101" }, "namespace": false } ================================================ FILE: src/Future.res ================================================ type getFn<'a> = ('a => unit) => unit type executorType = [#none | #trampoline] type t<'a> = Future(getFn<'a>, executorType) let trampoline = { let running = ref(false) let callbacks = [] let rec runLoop = callback => { callback() switch Array.pop(callbacks) { | None => () | Some(callback) => runLoop(callback) } } callback => if running.contents { ignore(Array.unshift(callbacks, callback)) } else { running := true runLoop(callback) running := false } } let make = (~executor: executorType=#none, resolver) => { let callbacks = ref(list{}) let data = ref(None) let runCallback = switch executor { | #none => (result, cb) => cb(result) | #trampoline => (result, cb) => trampoline(() => cb(result)) } resolver(result => switch data.contents { | None => data := Some(result) callbacks.contents->List.reverse->List.forEach(cb => runCallback(result, cb)) /* Clean up memory usage */ callbacks := list{} | Some(_) => () /* Do nothing; theoretically not possible */ } )->ignore Future( resolve => switch data.contents { | Some(result) => runCallback(result, resolve) | None => callbacks := list{resolve, ...callbacks.contents} }, executor, ) } let value = (~executor: executorType=#none, x) => make(~executor, resolve => resolve(x)) let map = (Future(get, executor), f) => make(~executor, resolve => get(result => resolve(f(result)))) let flatMap = (Future(get, executor), f) => make(~executor, resolve => get(result => { let Future(get2, _) = f(result) get2(resolve) }) ) let map2 = (fa, fb, f) => flatMap(fa, a => map(fb, b => f(a, b))) let map3 = (fa, fb, fc, f) => map2(map2(fa, fb, (a, b) => (a, b)), fc, ((a, b), c) => f(a, b, c)) let map4 = (fa, fb, fc, fd, f) => map2(map3(fa, fb, fc, (a, b, c) => (a, b, c)), fd, ((a, b, c), d) => f(a, b, c, d)) let map5 = (fa, fb, fc, fd, fe, f) => map2(map4(fa, fb, fc, fd, (a, b, c, d) => (a, b, c, d)), fe, ((a, b, c, d), e) => f(a, b, c, d, e) ) let rec all = futures => switch futures { | list{head, ...tail} => all(tail)->flatMap(tailResult => head->map(headResult => list{headResult, ...tailResult})) | list{} => value(list{}) } let tap = (Future(get, _) as future, f) => { get(f) future } let get = (Future(getFn, _), f) => getFn(f) /** * Future Result convenience functions, * for working with a type Future.t( result('a,'b) ) */ let mapOk = (future, f) => future->map(r => Result.map(r, f)) let mapError = (future, f) => future->map(r => switch r { | Error(v) => Error(f(v)) | Ok(a) => Ok(a) } ) let flatMapOk = (future, f) => future->flatMap(r => switch r { | Ok(v) => f(v) | Error(e) => value(Error(e)) } ) let flatMapOkPure = (fut, f) => fut->flatMapOk(result => value(result->f)) let flatMapError = (future, f) => future->flatMap(r => switch r { | Ok(v) => value(Ok(v)) | Error(e) => f(e) } ) let mapOk2 = (fa, fb, f) => flatMapOk(fa, a => mapOk(fb, b => f(a, b))) let mapOk3 = (fa, fb, fc, f) => mapOk2(mapOk2(fa, fb, (a, b) => (a, b)), fc, ((a, b), c) => f(a, b, c)) let mapOk4 = (fa, fb, fc, fd, f) => mapOk2(mapOk3(fa, fb, fc, (a, b, c) => (a, b, c)), fd, ((a, b, c), d) => f(a, b, c, d)) let mapOk5 = (fa, fb, fc, fd, fe, f) => mapOk2(mapOk4(fa, fb, fc, fd, (a, b, c, d) => (a, b, c, d)), fe, ((a, b, c, d), e) => f(a, b, c, d, e) ) let tapOk = (future, f) => future->tap(r => switch r { | Ok(v) => f(v)->ignore | Error(_) => () } ) let tapError = (future, f) => future->tap(r => switch r { | Error(v) => f(v)->ignore | Ok(_) => () } ) let delay = (~executor=?, ms, f) => make(~executor?, resolve => setTimeout(() => resolve(f()), ms)->ignore) let sleep = (~executor=?, ms) => delay(~executor?, ms, () => ()) let \">>=" = flatMapOk let \">>==" = flatMapOkPure let \"<$>" = mapOk ================================================ FILE: src/Future.resi ================================================ type getFn<'a> = ('a => unit) => unit type executorType = [#none | #trampoline] type t<'a> = Future(getFn<'a>, executorType) let trampoline: (unit => unit) => unit let make: (~executor: executorType=?, ('a => unit) => 'b) => t<'a> let value: (~executor: executorType=?, 'a) => t<'a> let map: (t<'a>, 'a => 'b) => t<'b> let flatMap: (t<'a>, 'a => t<'b>) => t<'b> let map2: (t<'a>, t<'b>, ('a, 'b) => 'c) => t<'c> let map3: (t<'a>, t<'b>, t<'c>, ('a, 'b, 'c) => 'd) => t<'d> let map4: (t<'a>, t<'b>, t<'c>, t<'d>, ('a, 'b, 'c, 'd) => 'e) => t<'e> let map5: (t<'a>, t<'b>, t<'c>, t<'d>, t<'e>, ('a, 'b, 'c, 'd, 'e) => 'f) => t<'f> let all: list> => t> let tap: (t<'a>, 'a => unit) => t<'a> let get: (t<'a>, 'a => unit) => unit let mapOk: (t>, 'a => 'c) => t> let mapError: (t>, 'b => 'c) => t> let flatMapOk: (t>, 'a => t>) => t> let flatMapOkPure: (t>, 'a => result<'c, 'b>) => t> let flatMapError: (t>, 'b => t>) => t> let mapOk2: (t>, t>, ('a, 'c) => 'd) => t> let mapOk3: ( t>, t>, t>, ('a, 'c, 'd) => 'e, ) => t> let mapOk4: ( t>, t>, t>, t>, ('a, 'c, 'd, 'e) => 'f, ) => t> let mapOk5: ( t>, t>, t>, t>, t>, ('a, 'c, 'd, 'e, 'f) => 'g, ) => t> let tapOk: (t>, 'a => 'c) => t> let tapError: (t>, 'b => 'c) => t> let delay: (~executor: executorType=?, int, unit => 'a) => t<'a> let sleep: (~executor: executorType=?, int) => t let \">>=": (t>, 'a => t>) => t> let \">>==": (t>, 'a => result<'c, 'b>) => t> let \"<$>": (t>, 'a => 'c) => t> ================================================ FILE: src/FutureJs.res ================================================ @ocaml.doc(" * Translate a Promise to a Future(result) * * errorTransformer: (Promise.error) => 'a * - The errorTransformer will provide you with the raw Promise.error * object. This is done so that you may decide on the appropriate error * handling scheme for your application. * See: http://keleshev.com/composable-error-handling-in-ocaml * - A good start is translating the Promise.error to a string. * ```reason * let errorTransformer = (error) => * String.make(error) * ->(str => /*... do your transforms here */ str); * ``` ") let fromPromise = (promise, errorTransformer) => Future.make(callback => promise ->Promise.then(res => Promise.resolve(ignore(callback(Ok(res))))) ->Promise.catch(error => Promise.resolve( ignore(callback((transformed => Error(transformed))(errorTransformer(error)))), ) ) ) let toPromise = future => Promise.make((resolve, _) => future->Future.get(value => resolve(value))) exception FutureError let resultToPromise = future => Promise.make((resolve, reject) => future ->Future.mapError(_ => FutureError) ->Future.map(result => switch result { | Ok(result) => resolve(result) | Error(error) => reject(error) } ) ->ignore ) ================================================ FILE: src/FutureJs.resi ================================================ let fromPromise: (RescriptCore.Promise.t<'a>, exn => 'b) => Future.t> let toPromise: Future.t<'a> => RescriptCore.Promise.t<'a> type exn += FutureError let resultToPromise: Future.t> => RescriptCore.Promise.t<'a> ================================================ FILE: src/FutureResult.res ================================================ let deprecate = (ft, name, more) => ft->Future.tap(_ => Console.warn("FutureResult." ++ (name ++ (" is deprecated." ++ more)))) let mapOk = (future, f) => future ->Future.map(r => switch r { | Ok(v) => f(v) | Error(e) => Error(e) } ) ->deprecate("mapOk", " Please use Future.mapOk instead.") let flatMapOk = (future, f) => future ->Future.flatMap(r => switch r { | Ok(v) => f(v) | Error(e) => Future.value(Error(e)) } ) ->deprecate("flatMapOk", "") let mapError = (future, f) => future ->Future.map(r => switch r { | Error(v) => f(v) | Ok(a) => Ok(a) } ) ->deprecate("mapError", " Please use Future.mapError instead.") let flatMapError = (future, f) => future ->Future.flatMap(r => switch r { | Error(v) => f(v) | Ok(a) => Future.value(Ok(a)) } ) ->deprecate("mapOk", "") let tapOk = (future, f) => future ->Future.tap(r => switch r { | Ok(v) => f(v)->ignore | Error(_) => () } ) ->deprecate("tapOk", " Please use Future.tapOk instead.") let tapError = (future, f) => future ->Future.tap(r => switch r { | Error(v) => f(v)->ignore | Ok(_) => () } ) ->deprecate("tapError", " Please use Future.tapError instead.")