UNPKG

reason-future

Version:

A lightweight, functional alternative to Js.Promise for ReScript, designed for better composability and error handling.

188 lines (129 loc) 6.88 kB
[![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