An open API service indexing awesome lists of open source software.

https://github.com/RationalJS/future

A Js.Promise alternative for ReScript
https://github.com/RationalJS/future

Last synced: 8 months ago
JSON representation

A Js.Promise alternative for ReScript

Awesome Lists containing this project

README

          

[![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