This is the latest docs version
Quick Links
  • -Overview
  • -Language Features
  • -JS Interop
  • -Build System
Documentation
Language Manual
Reference for all language features
ReScript & React
First class bindings for ReactJS
GenType
Seamless TypeScript integration
Reanalyze
Dead Code & Termination analysis
Exploration
Packages
Explore third party libraries and bindings
Syntax Lookup
Discover all syntax constructs
APIPlaygroundBlogCommunity
  • Playground
  • Blog
  • Twitter
  • GitHub
  • Forum
Language Manual
Overview
  • Introduction
  • Installation
  • Migrate to v11
  • Editor Plugins
  • Try
Language Features
  • Overview
  • Let Binding
  • Type
  • Primitive Types
  • Tuple
  • Record
  • Object
  • Variant
  • Polymorphic Variant
  • Null, Undefined and Option
  • Array & List
  • Function
  • If-Else & Loops
  • Pipe
  • Pattern Matching / Destructuring
  • Mutation
  • JSX
  • Exception
  • Lazy Value
  • Promises
  • Async / Await
    • How it looks
    • Basics
    • Types and async functions
    • Error handling
    • Piping await calls
    • Pattern matching on await calls
    • await multiple promises
    • JS Interop with async functions
  • Tagged templates
  • Module
  • Import & Export
  • Attribute (Decorator)
  • Reserved Keywords
  • Equality and Comparison
Advanced Features
  • Extensible Variant
  • Scoped Polymorphic Types
JavaScript Interop
  • Interop Cheatsheet
  • Embed Raw JavaScript
  • Shared Data Types
  • External (Bind to Any JS Library)
  • Bind to JS Object
  • Bind to JS Function
  • Import from / Export to JS
  • Bind to Global JS Values
  • JSON
  • Inlining Constants
  • Use Illegal Identifier Names
  • Generate Converters & Helpers
  • Browser Support & Polyfills
  • Libraries & Publishing
  • TypeScript
Build System
  • Overview
  • Configuration
  • Configuration Schema
  • External Stdlib
  • Pinned Dependencies
  • Interop with JS Build Systems
  • Performance
  • Warning Numbers
Guides
  • Converting from JS
Extra
  • Newcomer Examples
  • Project Structure
  • FAQ
Docs / Language Manual / Async / Await
Edit
RES
@val external fetchUserMail: string => promise<string> = "GlobalAPI.fetchUserMail" @val external sendAnalytics: string => promise<unit> = "GlobalAPI.sendAnalytics"

Async / Await

ReScript comes with async / await support to make asynchronous, Promise based code easier to read and write. This feature is very similar to its JS equivalent, so if you are already familiar with JS' async / await, you will feel right at home.

How it looks

Let's start with a quick example to show-case the syntax:

ReScriptJS Output
// Some fictive functionality that offers asynchronous network actions
@val external fetchUserMail: string => promise<string> = "GlobalAPI.fetchUserMail"
@val external sendAnalytics: string => promise<unit> = "GlobalAPI.sendAnalytics"

// We use the `async` keyword to allow the use of `await` in the function body
let logUserDetails = async (userId: string) => {
  // We use `await` to fetch the user email from our fictive user endpoint
  let email = await fetchUserMail(userId)

  await sendAnalytics(`User details have been logged for ${userId}`)

  Console.log(`Email address for user ${userId}: ${email}`)
}

As we can see above, an async function is defined via the async keyword right before the function's parameter list. In the function body, we are now able to use the await keyword to explicitly wait for a Promise value and assign its content to a let binding email.

You will probably notice that this looks very similar to async / await in JS, but there are still a few details that are specific to ReScript. The next few sections will go through all the details that are specific to the ReScript type system.

Basics

  • You may only use await in async function bodies

  • await may only be called on a promise value

  • await calls are expressions, therefore they can be used in pattern matching (switch)

  • A function returning a promise<'a> is equivalent to an async function returning a value 'a (important for writing signature files and bindings)

  • promise values and types returned from an async function don't auto-collapse into a "flat promise" like in JS (more on this later)

Types and async functions

async function type signatures

Function type signatures (i.e defined in signature files) don't require any special keywords for async usage. Whenever you want to type an async function, use a promise return type.

RESI
// Demo.resi let fetchUserMail: string => promise<string>

The same logic applies to type definitions in .res files:

RES
// function type type someAsyncFn = int => promise<int> // Function type annotation let fetchData: string => promise<string> = async (userId) => { await fetchUserMail(userId) }

BUT: When typing async functions in your implementation files, you need to omit the promise<'a> type:

RES
// This function is compiled into a `string => promise<string>` type. // The promise<...> part is implicitly added by the compiler. let fetchData = async (userId: string): string => { await fetchUserMail("test") }

For completeness reasons, let's expand the full signature and inline type definitions in one code snippet:

RES
// Note how the inline return type uses `string`, while the type definition uses `promise<string>` let fetchData: string => promise<string> = async (userId: string): string { await fetchUserMail(userId) }

Note: In a practical scenario you'd either use a type signature, or inline types, not both at the same time. In case you are interested in the design decisions, check out this discussion.

Promises don't auto-collapse in async functions

In JS, nested promises (i.e. promise<promise<'a>>) will automatically collapse into a flat promise (promise<'a>). This is not the case in ReScript. Use the await function to manually unwrap any nested promises within an async function instead.

RES
let fetchData = async (userId: string): string => { // We can't just return the result of `fetchUserMail`, otherwise we'd get a // type error due to our function return type of type `string` await fetchUserMail(userId) }

Error handling

You may use try / catch or switch to handle exceptions during async execution.

RES
// For simulation purposes let authenticate = async () => { raise(Exn.raiseRangeError("Authentication failed.")) } let checkAuth = async () => { try { await authenticate() } catch { | Exn.Error(e) => switch Exn.message(e) { | Some(msg) => Console.log("JS error thrown: " ++ msg) | None => Console.log("Some other exception has been thrown") } } }

Note how we are essentially catching JS errors the same way as described in our Exception section.

You may unify error and value handling in a single switch as well:

RES
let authenticate = async () => { raise(Exn.raiseRangeError("Authentication failed.")) } let checkAuth = async () => { switch await authenticate() { | _ => Console.log("ok") | exception Exn.Error(e) => switch Exn.message(e) { | Some(msg) => Console.log("JS error thrown: " ++ msg) | None => Console.log("Some other exception has been thrown") } } }

Important: When using await with a switch, always make sure to put the actual await call in the switch expression, otherwise your await error will not be caught.

Piping await calls

You may want to pipe the result of an await call right into another function. This can be done by wrapping your await calls in a new {} closure.

ReScriptJS Output
@val external fetchUserMail: string => promise<string> = "GlobalAPI.fetchUserMail"

let fetchData = async () => {
  let mail = {await fetchUserMail("1234")}->String.toUpperCase
  Console.log(`All upper-cased mail: ${mail}`)
}

Note how the original closure was removed in the final JS output. No extra allocations!

Pattern matching on await calls

await calls are just another kind of expression, so you can use switch pattern matching for more complex logic.

ReScriptJS Output
@val external fetchUserMail: string => promise<string> = "GlobalAPI.fetchUserMail"

let fetchData = async () => {
  switch (await fetchUserMail("user1"), await fetchUserMail("user2")) {
  | (user1Mail, user2Mail) => {
      Console.log("user 1 mail: " ++ user1Mail)
      Console.log("user 2 mail: " ++ user2Mail)
    }

  | exception JsError(err) => Console.log2("Some error occurred", err)
  }
}

await multiple promises

We can utilize the Promise module to handle multiple promises. E.g. let's use Promise.all to wait for multiple promises before continuing the program:

RES
let pauseReturn = (value, timeout) => { Promise.make((resolve, _reject) => { setTimeout(() => { resolve(value) }, timeout)->ignore }) } let logMultipleValues = async () => { let promise1 = pauseReturn("value1", 2000) let promise2 = pauseReturn("value2", 1200) let promise3 = pauseReturn("value3", 500) let all = await Promise.all([promise1, promise2, promise3]) switch all { | [v1, v2, v3] => Console.log(`All values: ${v1}, ${v2}, ${v3}`) | _ => Console.log("this should never happen") } }

JS Interop with async functions

async / await practically works with any function that returns a promise<'a> value. Map your promise returning function via an external, and use it in an async function as usual.

Here's a full example of using the MDN fetch API, using async / await to simulate a login:

RES
// A generic Response type for typing our fetch requests module Response = { type t<'data> @send external json: t<'data> => promise<'data> = "json" } // A binding to our globally available `fetch` function. `fetch` is a // standardized function to retrieve data from the network that is available in // all modern browsers. @val @scope("globalThis") external fetch: ( string, 'params, ) => promise<Response.t<{"token": Nullable.t<string>, "error": Nullable.t<string>}>> = "fetch" // We now use our asynchronous `fetch` function to simulate a login. // Note how we use `await` with regular functions returning a `promise`. let login = async (email: string, password: string) => { let body = { "email": email, "password": password, } let params = { "method": "POST", "headers": { "Content-Type": "application/json", }, "body": Json.stringifyAny(body), } try { let response = await fetch("https://reqres.in/api/login", params) let data = await response->Response.json switch Nullable.toOption(data["error"]) { | Some(msg) => Error(msg) | None => switch Nullable.toOption(data["token"]) { | Some(token) => Ok(token) | None => Error("Didn't return a token") } } } catch { | _ => Error("Unexpected network error occurred") } }
PromisesTagged templates

© 2024 The ReScript Project

Software and assets distribution powered by KeyCDN.

About
  • Community
  • ReScript Association
Find us on