Promise
Light and type-safe binding to JS promises
Install / Use
/learn @aantron/PromiseREADME
Promise

A lightweight, type-safe binding to JS promises:
Js.log(Promise.resolved("Hello")); /* Promise { 'Hello' } */
Promise.resolved("Hello")
->Promise.map(s => s ++ " world!")
->Promise.get(s => Js.log(s)); /* Hello world! */
As you can see on the first line, Promise.t maps directly to familiar JS
promises from your JS runtime. That means...
- You can use
reason-promisedirectly to write JS bindings. - All JS tooling for promises immediately works with
reason-promise. - Even if you do something exotic, like switch out the promise implementation at
the JS level, for, say, better stack traces,
reason-promisestill binds to it!
There is only one exception to the rule that Promise.t maps directly to JS
promises: when there is a promise nested inside another promise. JS breaks the
type safety of promises in a misguided attempt to
disallow nesting. reason-promise instead emulates it in a way that makes
promises type-safe again. This is in contrast to BuckleScript's
built-in Js.Promise, which directly exposes the JS behavior, and so is not
type-safe.
In addition:
reason-promiseoffers a clean functional API, which replaces rejection with helpers forResultandOption.reason-promiseis tiny. It weighs in at about 1K bundled.reason-promisealso has a full, standalone pure-OCaml implementation, which passes all the same tests. It can be used for native code or in JS.
Tutorial
- Installing
- Getting started
- Creating new promises
- Getting values from promises
- Transforming promises
- Tracing
- Concurrent combinations
- Handling errors with
Result - Advanced: Rejection
- Advanced: Bindings
- Discussion: Why JS promises are unsafe
- Discussion: How
reason-promisemakes promises type-safe
<a id="Installing"></a>
Installing
npm install reason-promise
Then, add reason-promise to your bsconfig.json:
{
"bs-dependencies": [
"reason-promise"
]
}
<br/>
<a id="GettingStarted"></a>
Getting started
To quickly get a project for pasting the code examples, clone the
[example repo][example-repo]. The code is in main.re.
git clone https://github.com/aantron/promise-example-bsb
cd promise-example-bsb
npm install
npm run test # To run each example.
There it also an example repo with [a trivial binding to parts of node-fetch][example-binding].
While reading the tutorial, it can be useful to glance at the [type signatures][rei] of the functions from time to time. They provide a neat summary of what each function does and what it expects from its callback.
<br/><a id="Creating"></a>
Creating new promises
The most basic function for creating a new promise is
[Promise.pending][pending]:
let (p, resolve) = Promise.pending()
Js.log(p) /* Promise { <pending> } */
The second value returned, resolve, is a function for resolving the promise:
let (p, resolve) = Promise.pending()
resolve("Hello")
Js.log(p) /* Promise { 'Hello' } */
[Promise.resolved][resolved] is a helper that returns an already-resolved
promise:
let p = Promise.resolved("Hello")
Js.log(p) /* Promise { 'Hello' } */
...and [Promise.exec][exec] is for wrapping functions that take callbacks:
@bs.val external setTimeout: (unit => unit, int) => unit = "setTimeout"
let p = Promise.exec(resolve => setTimeout(resolve, 1000))
Js.log(p) /* Promise { <pending> } */
/* Program then waits for one second before exiting. */
<br/>
<a id="Values"></a>
Getting values from promises
To do something once a promise is resolved, use [Promise.get][get]:
let (p, resolve) = Promise.pending()
p->Promise.get(s => Js.log(s))
resolve("Hello") /* Prints "Hello". */
<br/>
<a id="Transforming"></a>
Transforming promises
Use [Promise.map][map] to transform the value inside a promise:
let (p, resolve) = Promise.pending()
p
->Promise.map(s => s ++ " world")
->Promise.get(s => Js.log(s))
resolve("Hello") /* Hello world */
To be precise, Promise.map creates a new promise with the transformed value.
If the function you are using to transform the value also returns a promise,
use [Promise.flatMap][flatMap] instead of Promise.map. Promise.flatMap
will flatten the nested promise.
<a id="Tracing"></a>
Tracing
If you have a chain of promise operations, and you'd like to inspect the value
in the middle of the chain, use [Promise.tap][tap]:
let (p, resolve) = Promise.pending()
p
->Promise.tap(s => Js.log("Value is now: " ++ s))
->Promise.map(s => s ++ " world")
->Promise.tap(s => Js.log("Value is now: " ++ s))
->Promise.get(s => Js.log(s))
resolve("Hello")
/*
Value is now: Hello
Value is now: Hello world
Hello world
*/
<br/>
<a id="Combining"></a>
Concurrent combinations
[Promise.race][race] waits for one of the promises passed to it to resolve:
@bs.val external setTimeout: (unit => unit, int) => unit = "setTimeout"
let one_second = Promise.exec(resolve => setTimeout(resolve, 1000))
let five_seconds = Promise.exec(resolve => setTimeout(resolve, 5000))
Promise.race([one_second, five_seconds])
->Promise.get(() => {
Js.log("Hello")
exit(0)
})
/* Prints "Hello" after one second. */
[Promise.all][all] instead waits for all of the promises passed to it,
concurrently:
@bs.val external setTimeout: (unit => unit, int) => unit = "setTimeout"
let one_second = Promise.exec(resolve => setTimeout(resolve, 1000))
let five_seconds = Promise.exec(resolve => setTimeout(resolve, 5000))
Promise.all([one_second, five_seconds])
->Promise.get(_ => {
Js.log("Hello")
exit(0)
})
/* Prints "Hello" after five seconds. */
For convenience, there are several variants of Promise.all:
- [
Promise.all2][all2] - [
Promise.all3][all3] - [
Promise.all4][all4] - [
Promise.all5][all5] - [
Promise.all6][all6] - [
Promise.allArray][allArray]
<a id="Errors"></a>
Handling errors with Result
Promises that can fail are represented using the standard library's
[Result][Result], and its constructors Ok and Error:
open Belt.Result
Promise.resolved(Ok("Hello"))
->Promise.getOk(s => Js.log(s)) /* Hello */
[Promise.getOk][getOk] waits for p to have a value, and runs its function
only if that value is Ok(_). If you instead resolve the promise with
Error(_), there will be no output:
open Belt.Result
Promise.resolved(Error("Failed"))
->Promise.getOk(s => Js.log(s)) /* Program just exits. */
You can wait for either kind of value by calling [Promise.getOk][getOk] and
[Promise.getError][getError]:
open Belt.Result
let () = {
let p = Promise.resolved(Error("Failed"))
p->Promise.getOk(s => Js.log(s))
p->Promise.getError(s => Js.log("Error: " ++ s))
} /* Error: Failed */
...or respond to all outcomes using the ordinary [Promise.get][get]:
open Belt.Result
Promise.resolved(Error("Failed"))
->Promise.get(result =>
switch result {
| Ok(s) => Js.log(s)
| Error(s) => Js.log("Error: " ++ s)
}) /* Error: Failed */
The full set of functions for handling results is:
- [
Promise.getOk][getOk] - [
Promise.tapOk][tapOk] - [
Promise.mapOk][mapOk] - [
Promise.flatMapOk][flatMapOk] - [
Promise.getError][getError] - [
Promise.tapError][tapError] - [
Promise.mapError][mapError] - [
Promise.flatMapError][flatMapError]
There are also similar functions for working with [Option][Option]:
- [
Promise.getSome][getSome] - [
Promise.tapSome][tapSome] - [
Promise.mapSome][mapSome] - [
Promise.flatMapSome][flatMapSome]
In addition, there is also a set of variants of Promise.all for results, which
propagate any Error(_) as soon as it is received:
- [
Promise.allOk][allOk] - [
Promise.allOk2][allOk2] - [
Promise.allOk3][allOk3] - [
Promise.allOk4][allOk4] - [
Promise.allOk5][allOk5] - [
Promise.allOk6][allOk6] - [
Promise.allOkArray][allOkArray]
If you'd like instead to fully wait for all the promises to resolve with either
Ok(_) or Error(_), you can use the ordinary Promise.all and its variants.
<a id="Rejection"></a>
Advanced: Rejection
As you can see from Handling errors, Promise doesn't use rejection
for errors — but JavaScript promises do. In order to support bindings to
JavaScript libraries, which often return promises that can be rejected,
Promise provides the [Promise.Js][Promise.Js] helper module.
Promise.Js works the same way as Promise. It similarly has:
- [
Promise.Js.get][Js.get] - [
Promise.Js.tap][Js.tap] - [
Promise.Js.map][Js.map] - [
Promise.Js.flatMap][Js.flatMap]
However, because Promise.Js uses JS rejection for error handling rather than
Result or Option,
- There are no helpers for
Related Skills
node-connect
338.7kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
83.6kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
338.7kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
commit-push-pr
83.6kCommit, push, and open a PR
