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
================================================
[](https://www.npmjs.com/package/reason-future)
[](https://travis-ci.org/RationalJS/future)
[](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<'a>> => t<list<'a>>
let tap: (t<'a>, 'a => unit) => t<'a>
let get: (t<'a>, 'a => unit) => unit
let mapOk: (t<result<'a, 'b>>, 'a => 'c) => t<result<'c, 'b>>
let mapError: (t<result<'a, 'b>>, 'b => 'c) => t<result<'a, 'c>>
let flatMapOk: (t<result<'a, 'b>>, 'a => t<result<'c, 'b>>) => t<result<'c, 'b>>
let flatMapOkPure: (t<result<'a, 'b>>, 'a => result<'c, 'b>) => t<result<'c, 'b>>
let flatMapError: (t<result<'a, 'b>>, 'b => t<result<'a, 'c>>) => t<result<'a, 'c>>
let mapOk2: (t<result<'a, 'b>>, t<result<'c, 'b>>, ('a, 'c) => 'd) => t<result<'d, 'b>>
let mapOk3: (
t<result<'a, 'b>>,
t<result<'c, 'b>>,
t<result<'d, 'b>>,
('a, 'c, 'd) => 'e,
) => t<result<'e, 'b>>
let mapOk4: (
t<result<'a, 'b>>,
t<result<'c, 'b>>,
t<result<'d, 'b>>,
t<result<'e, 'b>>,
('a, 'c, 'd, 'e) => 'f,
) => t<result<'f, 'b>>
let mapOk5: (
t<result<'a, 'b>>,
t<result<'c, 'b>>,
t<result<'d, 'b>>,
t<result<'e, 'b>>,
t<result<'f, 'b>>,
('a, 'c, 'd, 'e, 'f) => 'g,
) => t<result<'g, 'b>>
let tapOk: (t<result<'a, 'b>>, 'a => 'c) => t<result<'a, 'b>>
let tapError: (t<result<'a, 'b>>, 'b => 'c) => t<result<'a, 'b>>
let delay: (~executor: executorType=?, int, unit => 'a) => t<'a>
let sleep: (~executor: executorType=?, int) => t<unit>
let \">>=": (t<result<'a, 'b>>, 'a => t<result<'c, 'b>>) => t<result<'c, 'b>>
let \">>==": (t<result<'a, 'b>>, 'a => result<'c, 'b>) => t<result<'c, 'b>>
let \"<$>": (t<result<'a, 'b>>, 'a => 'c) => t<result<'c, 'b>>
================================================
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<result<'a, 'b>>
let toPromise: Future.t<'a> => RescriptCore.Promise.t<'a>
type exn += FutureError
let resultToPromise: Future.t<result<'a, 'b>> => 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.")
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
Condensed preview — 15 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (35K chars).
[
{
"path": ".gitignore",
"chars": 115,
"preview": ".DS_Store\n.merlin\n.bsb.lock\nnpm-debug.log\n/lib/bs/\n/node_modules/\npackage-lock.json\n*.bs.js\n.nyc_output/\ncoverage/\n"
},
{
"path": ".npmignore",
"chars": 100,
"preview": ".DS_Store\n.merlin\n.bsb.lock\nnpm-debug.log\n/lib/bs/\n/node_modules/\ntests/\npackage-lock.json\n.vscode/\n"
},
{
"path": ".travis.yml",
"chars": 49,
"preview": "language: node_js\nnode_js:\n - lts/*\ncache: yarn\n"
},
{
"path": ".vscode/tasks.json",
"chars": 2010,
"preview": "{\n \"version\": \"2.0.0\",\n \"command\": \"npm\",\n \"options\": {\n \"cwd\": \"${workspaceRoot}\"\n },\n \"type\": \"she"
},
{
"path": "README.md",
"chars": 6863,
"preview": "[](https://www.npmjs.com/package/reason-future)\n[ => {\n testAsync(\"sync chaining\", finish =>\n Future.value(\"one\")\n ->F"
},
{
"path": "__tests__/TestFutureJs.res",
"chars": 3446,
"preview": "open Jest\nopen Expect\n\nexception TestError(string)\n\ndescribe(\"FutureJs\", () => {\n let errorTransformer = x => x\n\n test"
},
{
"path": "jest.config.js",
"chars": 175,
"preview": "module.exports = {\n testPathIgnorePatterns: [\"/node_modules/\", \"__tests__/TestUtil.bs.js\"],\n coveragePathIgnorePattern"
},
{
"path": "package.json",
"chars": 1216,
"preview": "{\n \"name\": \"reason-future\",\n \"version\": \"3.0.0\",\n \"scripts\": {\n \"build\": \"rescript build -with-deps\",\n \"start\":"
},
{
"path": "rescript.json",
"chars": 477,
"preview": "{\n \"name\": \"reason-future\",\n \"version\": \"3.0.0\",\n \"sources\": [\n {\n \"dir\": \"src\",\n \"subdirs\": true\n },"
},
{
"path": "src/Future.res",
"chars": 3996,
"preview": "type getFn<'a> = ('a => unit) => unit\n\ntype executorType = [#none | #trampoline]\n\ntype t<'a> = Future(getFn<'a>, executo"
},
{
"path": "src/Future.resi",
"chars": 2106,
"preview": "type getFn<'a> = ('a => unit) => unit\ntype executorType = [#none | #trampoline]\ntype t<'a> = Future(getFn<'a>, executorT"
},
{
"path": "src/FutureJs.res",
"chars": 1283,
"preview": "@ocaml.doc(\"\n * Translate a Promise to a Future(result)\n *\n * errorTransformer: (Promise.error) => 'a\n * - The errorTran"
},
{
"path": "src/FutureJs.resi",
"chars": 244,
"preview": "let fromPromise: (RescriptCore.Promise.t<'a>, exn => 'b) => Future.t<result<'a, 'b>>\nlet toPromise: Future.t<'a> => Resc"
},
{
"path": "src/FutureResult.res",
"chars": 1265,
"preview": "let deprecate = (ft, name, more) =>\n ft->Future.tap(_ => Console.warn(\"FutureResult.\" ++ (name ++ (\" is deprecated.\" ++"
}
]
About this extraction
This page contains the full source code of the RationalJS/future GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 15 files (31.5 KB), approximately 9.9k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.