title | description | canonical |
---|---|---|
Promises | JS Promise handling in ReScript | /docs/manual/v11.0.0/promise |
Note: Starting from ReScript 10.1 and above, we recommend using async / await when interacting with Promises.
Since 10.1
In ReScript, every JS promise is represented with the globally available promise<'a>
type. For ReScript versions < 10.1, use its original alias Js.Promise.t<'a>
instead.
Here's a usage example in a function signature:
// User.resi file type user = {name: string} let fetchUser: string => promise<user>
To work with promise values (instead of using async
/ await
) you may want to use the built-in Promise
module.
A builtin module to create, chain and manipulate promises.
letp1=Promise.make((resolve, reject) => { // We use uncurried functions for resolve / reject// for cleaner JS output without unintended curry callsresolve("hello world") }) letp2=Promise.resolve("some value") // You can only reject `exn` values for streamlined catch handlingexceptionMyOwnError(string) letp3=Promise.reject(MyOwnError("some rejection"))
letlogAsyncMessage= () => { openPromisePromise.resolve("hello world") ->then(msg=> { // then callbacks require the result to be resolved explicitlyresolve("Message: "++msg) }) ->then(msg=> { Console.log(msg) // Even if there is no result, we need to use resolve() to return a promiseresolve() }) ->ignore// Requires ignoring due to unhandled return value }
For comparison, the async
/ await
version of the same code would look like this:
letlogAsyncMessage=async () => { letmsg=awaitPromise.resolve("hello world") Console.log(`Message: ${msg}`) }
Needless to say, the async / await version offers better ergonomics and less opportunities to run into type issues.
You can handle a rejected promise using the Promise.catch()
method, which allows you to catch and manage errors effectively.
In case you want to launch multiple promises in parallel, use Promise.all
:
<CodeTab labels={["ReScript", "JS Output"]}>
@valexternalfetchMessage: string=>promise<string> ="global.fetchMessage"letlogAsyncMessage=async () => { letmessages=awaitPromise.all([fetchMessage("message1"), fetchMessage("message2")]) Console.log(messages->Array.joinWith(", ")) }
asyncfunctionlogAsyncMessage(param){varmessages=awaitPromise.all([global.fetchMessage("message1"),global.fetchMessage("message2")]);console.log(messages.join(", "));}export{logAsyncMessage,}
Note: The
Js.Promise
bindings are following the outdated data-last convention from a few years ago. We kept those APIs for backwards compatibility. Either usePromise
or a third-party promise binding instead.
ReScript has built-in support for JavaScript promises. The 3 functions you generally need are:
Js.Promise.resolve: 'a => Js.Promise.t<'a>
Js.Promise.then_: ('a => Js.Promise.t<'b>, Js.Promise.t<'a>) => Js.Promise.t<'b>
Js.Promise.catch: (Js.Promise.error => Js.Promise.t<'a>, Js.Promise.t<'a>) => Js.Promise.t<'a>
Additionally, here's the type signature for creating a promise on the ReScript side:
Js.Promise.make: ( ( ~resolve: (. 'a) =>unit, ~reject: (. exn) =>unit ) =>unit ) =>Js.Promise.t<'a>
This type signature means that make
takes a callback that takes 2 named arguments, resolve
and reject
. Both arguments are themselves uncurried callbacks (with a dot). make
returns the created promise.
Using the pipe operator:
<CodeTab labels={["ReScript", "JS Output"]}>
letmyPromise=Js.Promise.make((~resolve, ~reject) =>resolve(. 2)) myPromise->Js.Promise.then_(value=> { Console.log(value) Js.Promise.resolve(value+2) }, _)->Js.Promise.then_(value=> { Console.log(value) Js.Promise.resolve(value+3) }, _)->Js.Promise.catch(err=> { Console.log2("Failure!!", err) Js.Promise.resolve(-2) }, _)
varmyPromise=newPromise(function(resolve,reject){returnresolve(2);});myPromise.then(function(value){console.log(value);returnPromise.resolve((value+2)|0);}).then(function(value){console.log(value);returnPromise.resolve((value+3)|0);}).catch(function(err){console.log("Failure!!",err);returnPromise.resolve(-2);});