A type-safe promise type for ReasonML, built directly on top of JavaScript but adding fine-grained error control and promise cancellation.
Prometo is type-safe because of the following:
- It's not just a 'promise of your data', it's a 'promise of result of your data'. In other words, it can't be affected by JavaScript promises' well-known unsoundness issue where a 'promise of promise of data' is collapsed at runtime to a 'promise of data'.
- Also because a Prometo promise is a 'promise of result of data', it
encodes an error at the type-level using the Reason
result('a, 'e)
type. In fact, Prometo ensures that its wrapped promises are not rejected, as long as you use its operations. So you can be sure that a Prometo promise is actually not going to throw at runtime. The only point at which you need to care about catching a possible exception is when converting it back into a normal JavaScript promise.
Because it's just a JavaScript promise wrapper, it's also easy to convert back and forth between JavaScript and Prometo promises.
Using the technique described in
Composable Error Handling in OCaml,
Prometo exposes an error type 'e
directly in its main polymorphic
promise type Prometo.t('a, 'e)
. This allows you to explicitly track
and manage errors at every point of the code where you use these
promises.
It's easy to interop with JavaScript promises:
- Use
fromPromise
to convert from a JavaScript Promise to a Prometo promise toPromise
to convert from Prometo to a JavaScript promisethenPromise
to chain together a Prometo promise and a function that returns a JavaScript Promise, and keep the result as a type-safe Prometo promise.
Prometo promises can be cancelled at any point of usage in the code,
using Prometo.cancel
. The only caveat is that when you cancel a
promise, it doesn't immediately halt whatever is running inside that
promise, but lets it run until its result is used by the next flatMap
in the promise chain. At that point, the next promise automatically
turns into a cancelled promise, stopping whatever was about to happen
next from happening.
This also does mean that if you want to cancel a promise chain, you need to ensure that you keep a reference to the first promise in the chain and cancel that.
While not as immediate as something like
abortable fetch,
in practice this is more general-purpose (it works with any promise, not
just ones returned by fetch
), and it's enough to prevent errors like
calling setState
on an unmounted React component.
For example:
let example = fetch("https://example.com");
Prometo.forEach(~f=setState, example);
// later...
Prometo.cancel(example); // this will prevent setState from being called
Cancelling a promise too late in the chain won't work:
let result = "https://example.com"
|> fetch
|> Prometo.map(~f=setState);
// later...
Prometo.cancel(result); // won't work, too late, setState has already been called
- Add the
@yawaramin/prometo
project to yourpackage.json
(version number in badge at the top of the readme) - Add the
@yawaramin/prometo
dependency to yourbs-dependencies
list inbsconfig.json
- Run
npx bsb -clean-world
, thennpx bsb -make-world
- (Optional but
recmomended)
if you don't have one already, create a
Yawaramin.re
(or.ml
) file undersrc/
and alias the Prometo main module there:module Prometo = Yawaramin__Prometo
. This lets you access it under theYawaramin.Prometo
namespace throughout your project
Please go to https://yawaramin.github.io/prometo/