Skip to content

Commit 65844c9

Browse files
committed
Add rough async/await doc
1 parent 62634d2 commit 65844c9

File tree

3 files changed

+213
-2
lines changed

3 files changed

+213
-2
lines changed

data/sidebar_manual_latest.json

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"exception",
2828
"lazy-values",
2929
"promise",
30+
"async-await",
3031
"module",
3132
"import-export",
3233
"attribute",
+210
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
---
2+
title: "Async / Await"
3+
description: "Async / await for asynchronous operations"
4+
canonical: "/docs/manual/latest/async-await"
5+
---
6+
7+
<!-- See https://github.com/cristianoc/rescript-compiler-experiments/pull/1#issuecomment-1131182023 for all async/await use-case examples -->
8+
9+
# Async / Await
10+
11+
***Since 10.1***
12+
13+
Use the `async` / `await` keywords to make asynchronous, `Promise` based code easier to read and write. If you are already familiar with JS' `async` / `await`, you will most likely be able to use the syntax right away as is.
14+
15+
**Some basics:**
16+
- You may only use `await` in `async` function bodies
17+
- `await` may only be called on a `promise` value
18+
- `await` calls are expressions (pattern matching!)
19+
- A function returning a `promise<'a>` is equivalent to an `async` function returning a value `'a` (important for writing signature files and bindings)
20+
- `promise` values and types returned from an `async` function don't auto-collapse
21+
22+
## How it looks
23+
24+
Let's start with a quick example to show-case the syntax:
25+
26+
<CodeTab labels={["ReScript", "JS Output"]}>
27+
28+
```res
29+
// Some fictive functionality that offers asynchronous network actions
30+
@val external fetchUserMail: string => promise<string> = "GlobalAPI.fetchUserMail"
31+
@val external sendAnalytics: string => promise<unit> = "GlobalAPI.sendAnalytics"
32+
33+
// We use the `async` keyword to allow the use of `await` in the function body
34+
let logUserDetails = async (userId: string) => {
35+
// We use `await` to fetch the user email from our fictive user endpoint
36+
let email = await fetchUserMail(userId)
37+
38+
await sendAnalytics(`User details have been logged for ${userId}`)
39+
40+
Js.log(`Email address for user ${userId}: ${email}`)
41+
}
42+
```
43+
44+
```js
45+
async function logUserDetails(userId) {
46+
var email = await GlobalAPI.fetchUserMail(userId);
47+
await GlobalAPI.sendAnalytics("User details have been logged for " + userId + "");
48+
console.log("Email address for user " + userId + ": " + email + "");
49+
}
50+
```
51+
52+
</CodeTab>
53+
54+
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`.
55+
56+
Everything we've just saw was essentially what we are used to `async` / `await` in JS, but there's 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.
57+
58+
## Types and `async` functions
59+
60+
### No `promise` type in inline return types
61+
62+
When typing the return type of an `async` function inline, we completely omit the `promise<...>` type and just state the actual type we want to return. As an example, we would type a `logUserDetails` function like this:
63+
64+
65+
```res
66+
// Instead of promise<unit> we return `unit` instead.
67+
// The boxing into a promise is already done implicitly
68+
// by the compiler.
69+
let logUserDetails = async (userId: string): unit => {
70+
Js.log("...")
71+
}
72+
```
73+
74+
**Note:** This was a deliberate design decision. More details on the rationale can be found [here](https://github.com/rescript-lang/rescript-compiler/pull/5913#issuecomment-1359003870).
75+
76+
### Promises don't auto-collapse in async functions
77+
78+
As a JS developer you'd expect a `promise<'a>` to collapse into another `promise<'a>` when returned in an `async` function. This is not the case in ReScript. Use the `await` function to unwrap any nested promises instead.
79+
80+
```res
81+
let fetchData = async (userId: string): string => {
82+
// We can't just return the result of `fetchUserMail`, otherwise we'd get a
83+
// type error due to our function return type of type `string`
84+
await fetchUserMail(userId)
85+
}
86+
```
87+
88+
### `async` function type signatures
89+
90+
Function type signatures (i.e defined in signature files) don't differentiate between `async` and conventional functions. Every function with a `promise` return type are `async` functions; hence we use the `promise` return type.
91+
92+
```resi
93+
// Demo.resi
94+
95+
let fetchUserMail: string => promise<string>
96+
```
97+
98+
The same logic applies to type definitions in `.res` files:
99+
100+
```res
101+
// function type
102+
type someAsyncFn = int => promise<int>
103+
104+
// Function type annotation
105+
let fetchData: string => promise<string> = async (userId) => {
106+
await fetchUserMail(userId)
107+
}
108+
```
109+
110+
For completeness reasons, let's also show-case the difference between type definitions and inline type definitions:
111+
112+
```res
113+
// Note how the inline return type uses `string`, while the type definition uses `promise<string>`
114+
let fetchData: string => promise<string> = async (userId: string): string {
115+
await fetchuserMail(userId)
116+
}
117+
```
118+
119+
(The last example was only mentioned for education purposes. Don't do that in your actual code.)
120+
121+
## Common usage examples
122+
123+
### Error handling
124+
125+
As with any synchronous code, you may use `try / catch` or `switch` to pattern match on errors.
126+
127+
```res example
128+
let logUserDetails = async (userId: string): result<unit, string> => {
129+
let email = await fetchUserMail(userId)
130+
131+
// await can be used within a `try` body
132+
try {
133+
Js.log(`Email address for user ${userId}: ${email}`)
134+
await sendAnalytics(`User details have been logged for ${userId}`)
135+
Ok()
136+
} catch {
137+
// In case some generic JS exception has been thrown due to unknown interop reasons
138+
| JsError(_) => Error("Could not send analytics")
139+
}
140+
}
141+
```
142+
143+
### Piping `await` calls
144+
145+
It is possible
146+
147+
<CodeTab labels={["ReScript", "JS Output"]}>
148+
149+
```res example
150+
@val external fetchUserMail: string => promise<string> = "GlobalAPI.fetchUserMail"
151+
152+
let fetchData = async () => {
153+
let mail = {await fetchUserMail("1234")}->Js.String2.toUpperCase
154+
Js.log(`All upper-cased mail: ${mail}`)
155+
}
156+
```
157+
158+
```js
159+
async function fetchData(param) {
160+
var mail = (await GlobalAPI.fetchUserMail("1234")).toUpperCase();
161+
console.log("All upper-cased mail: " + mail + "");
162+
}
163+
```
164+
165+
</CodeTab>
166+
167+
### Pattern matching on `await` calls
168+
169+
Of course we can also go fancy with all kinds of pattern matching combinations.
170+
171+
<CodeTab labels={["ReScript", "JS Output"]}>
172+
173+
```res example
174+
@val external fetchUserMail: string => promise<string> = "GlobalAPI.fetchUserMail"
175+
176+
let fetchData = async () => {
177+
switch (await fetchUserMail("user1"), await fetchUserMail("user2")) {
178+
| (user1Mail, user2Mail) => {
179+
Js.log("user 1 mail: " ++ user1Mail)
180+
Js.log("user 2 mail: " ++ user2Mail)
181+
}
182+
183+
| exception JsError(err) => Js.log2("Some error occurred", err)
184+
}
185+
}
186+
```
187+
188+
```js
189+
async function fetchData(param) {
190+
var val;
191+
var val$1;
192+
try {
193+
val = await GlobalAPI.fetchUserMail("user1");
194+
val$1 = await GlobalAPI.fetchUserMail("user2");
195+
}
196+
catch (raw_err){
197+
var err = Caml_js_exceptions.internalToOCamlException(raw_err);
198+
if (err.RE_EXN_ID === "JsError") {
199+
console.log("Some error occurred", err._1);
200+
return ;
201+
}
202+
throw err;
203+
}
204+
console.log("user 1 mail: " + val);
205+
console.log("user 2 mail: " + val$1);
206+
}
207+
```
208+
209+
</CodeTab>
210+

pages/docs/manual/latest/promise.mdx

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
---
2-
title: "Async & Promise"
2+
title: "Promises"
33
description: "JS Promise handling in ReScript"
44
canonical: "/docs/manual/latest/promise"
55
---
66

7-
# Async & Promise
7+
# Promise
88

99
Support for `async` and `await` is added in compiler version 10.1. The majority of existing code is based on promises. The new Promise API bindings make async code look better than with old promises.
1010

0 commit comments

Comments
 (0)