This is a proposal for a better ReScript promise binding that unfortunately is not zero-cost, and introduces some small overhead.
See the PROPOSAL.md for the rationale and design decisions.
Quick feature summary:
t-first
bindings- Fully compatible with
Js.Promise.t
- Allows nested promises (no
resolve
call on eachthen
) - Has small runtime overhead for fixing nested promises
- No rejection tracking or other complex type hackery
- No special utilities (less things to maintain)
This binding aims to be as close to the JS Promise API as possible.
This is experimental and not published yet. Don't use it in production yet.
# added to see how an installation might look like
npm install @rescript/rescript-promise --save
Until npm release, use install directly from GH instead:
# via npm
npm install git+https://github.com/ryyppy/rescript-promise.git --save
# via yarn
yarn add ryyppy/rescript-promise
Add rescript-promise
as a dependency in your bsconfig.json
:
{
"bs-dependencies": [
"rescript-promise"
]
}
This will expose a global Promise
module (don't worry, it will not mess with your existing Js.Promise
code).
Creating a Promise:
let p1 = Promise.make((resolve, _reject) => {
resolve(. "hello world")
})
let p2 = Promise.resolve("some value")
// You can only reject `exn` values for streamlined catch handling
exception MyOwnError(string)
let p3 = Promise.reject(MyOwnError("some rejection"))
Chain promises:
let p = {
open Promise
Promise.resolve("hello world")
->then(msg => {
Js.log("Message: " ++ msg)
})
}
Chain nested promises:
type user = {"name": string}
type comment = string
@val external queryComments: string => Js.Promise.t<array<comment>> = "API.queryComments"
@val external queryUser: string => Js.Promise.t<user> = "API.queryUser"
let p = {
open Promise
queryUser("patrick")
->flatThen(user => {
// We use flatThen instead of then to automatically
// unnest our queryComments promise
queryComments(user["name"])
})
->then(comments => {
// comments is now an array<comment>
Belt.Array.forEach(comments, comment => Js.log(comment))
})
}
Catch promise errors:
exception MyError(string)
let p = {
open Promise
Promise.reject(MyError("test"))
->then(str => {
Js.log("this should not be reached: " ++ str)
})
->catch(e => {
// Promise.handleError: error => exn
switch handleError(e) {
| MyError(str) => Js.log("found MyError: " ++ str)
| _ => Js.log("Anything else")
}
})
}
Catch promise errors caused by a thrown JS exception:
let p = {
open Promise
let causeErr = () => {
Js.Exn.raiseError("Some JS error")
}
Promise.resolve()
->then(_ => {
causeErr()
})
->catch(e => {
switch handleError(e) {
| JsError(obj) =>
switch Js.Exn.message(obj) {
| Some(msg) => Js.log("Some JS error msg: " ++ msg)
| None => Js.log("Must be some non-error value")
}
}
})
}
Using a promise from JS:
@val external someAsyncApi: unit => Js.Promise.t<string> = "someAsyncApi"
someAsyncApi()->Promise.then((str) => Js.log(str))
Running multiple Promises concurrently:
let _ = {
open Promise
let place = ref(0)
let delayedMsg = (ms, msg) => {
Promise.make((resolve, _) => {
Js.Global.setTimeout(() => {
place := place.contents + 1
resolve(.(place.contents, msg))
}, ms)->ignore
})
}
let p1 = delayedMsg(1000, "is Anna")
let p2 = delayedMsg(500, "myName")
let p3 = delayedMsg(100, "Hi")
all([p1, p2, p3])->then(arr => {
// [ [ 3, 'is Anna' ], [ 2, 'myName' ], [ 1, 'Hi' ] ]
Belt.Array.map(arr, ((place, name)) => {
Js.log(`Place ${Belt.Int.toString(place)} => ${name}`)
})
// Output
// Place 3 => is Anna
// Place 2 => myName
// Place 1 => Hi
})
}
Race Promises:
let _ = {
open Promise
let racer = (ms, name) => {
Promise.make((resolve, _) => {
Js.Global.setTimeout(() => {
resolve(. name)
}, ms)->ignore
})
}
let promises = [racer(1000, "Turtle"), racer(500, "Hare"), racer(100, "Eagle")]
race(promises)->then(winner => {
Js.log("Congrats: " ++ winner)
// Congrats: Eagle
})
}
# Building
npm run build
# Watching
npm run dev
Runs all tests
node tests/PromiseTest.js
Heavily inspired by github.com/aantron/promise.