reason-future
v3.0.0
Published
A lightweight, functional alternative to Js.Promise for ReScript, designed for better composability and error handling.
Downloads
441
Readme
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
[email protected]
for ReasonML or ReScript ≤ v10. - Use
[email protected]
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
:
let futureGreeting = Future.make(resolve => resolve("hi"));
To get the value of a future, use Future.get
:
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
:
/* 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:
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 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 valueFuture.flatMap(_,fn)
- Transform a future value into another future valueFuture.get(_,fn)
- Get the value of a futureFuture.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 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 anResult.Ok
. Similar toPromise.prototype.then
Future.mapError(_,fn)
- Transform a future value into another value, but only if the value is anResult.Error
. Similar toPromise.prototype.catch
Future.tapOk(_,fn)
- Do something with the value of a future without changing it, but only if the value is aOk
. Returns the same future. Convenience for side effects such as console logging.Future.tapError(_,fn)
- Same astapOk
but forResult.Error
The following are more situational:
Future.flatMapOk(_, fn)
- Transform a futureResult.Ok
into a futureResult
. Flattens the inner future.Future.flatMapError(_, fn)
- Transform a futureResult.Error
into a futureResult
. Flattens the inner future.
FutureJs
Convenience functions for interop with JavaScript land.
FutureJs.fromPromise(promise, errorTransformer)
promise
is theRescriptCore.Promise.t('a)
that will be transformed into aFuture.t(result('a, 'e))
errorTransformer
allows you to determine howPromise.error
objects will be transformed before they are returned wrapped within aError
. This allows you to implement the error handling method which best meets your application's needs.
FutureJs.toPromise(future)
future
is anyFuture.t('a)
which is transformed into aRescriptCore.Promise.t('a)
. Always resolves, never rejects the promise.
FutureJs.resultToPromise(future)
future
is theFuture.t(result('a, 'e))
which is transformed into aRescriptCore.Promise.t('a)
. Resolves the promise on Ok result and rejects on Error result.
Example use:
/*
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 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:
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)
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