forked from ryyppy/rescript-promise
-
Notifications
You must be signed in to change notification settings - Fork 0
/
FetchExample.res
164 lines (144 loc) · 4.42 KB
/
FetchExample.res
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
// This is only needed for polyfilling the `fetch` API in
// node, so we can run this example on the commandline
module NodeFetchPolyfill = {
type t
@module external fetch: t = "node-fetch"
@val external globalThis: 'a = "globalThis"
globalThis["fetch"] = fetch
}
/*
In this example, we are accessing a REST endpoint by doing two async operations:
- Login with a valid user and retrieve a Bearer token
- Use the token in our next call to retrieve a list of products
We factor our code in two submodules: Login and Product.
Both modules bind to their own specialized version of `fetch` in the global scope,
and specify the return type to their resulting data structures.
Results are not formally verified (decoded), so we made type assumptions on our
incoming data, and depending on its results, return a `result` value to signal
error or success cases.
We also use some `catch` calls to either short-circuit operations that have failed,
or to catch failed operations to unify into a `result` value.
*/
// Fetch uses a `Response` object that offers a `res.json()` function to retrieve
// a json result. We use a json based api, so we create a binding to access this feature.
module Response = {
type t<'data>
@send external json: t<'data> => Promise.t<'data> = "json"
}
module Login = {
// This is our type assumption for a /login query return value
// In case the operation was successful, the response will contain a `token` field,
// otherwise it will return an `{"error": "msg"}` value that signals an unsuccessful login
type response = {"token": Js.Nullable.t<string>, "error": Js.Nullable.t<string>}
@val @scope("globalThis")
external fetch: (
string,
'params,
) => Promise.t<Response.t<{"token": Js.Nullable.t<string>, "error": Js.Nullable.t<string>}>> =
"fetch"
let login = (email: string, password: string) => {
open Promise
let body = {
"email": email,
"password": password,
}
let params = {
"method": "POST",
"headers": {
"Content-Type": "application/json",
},
"body": Js.Json.stringifyAny(body),
}
fetch("https://reqres.in/api/login", params)
->then(res => {
Response.json(res)
})
->map(data => {
// Notice our pattern match on the "error" / "token" fields
// to determine the final result. Be aware that this logic highly
// depends on the backend specificiation.
switch Js.Nullable.toOption(data["error"]) {
| Some(msg) => Error(msg)
| None =>
switch Js.Nullable.toOption(data["token"]) {
| Some(token) => Ok(token)
| None => Error("Didn't return a token")
}
}
})
->catch(e => {
let msg = switch e {
| JsError(err) =>
switch Js.Exn.message(err) {
| Some(msg) => msg
| None => ""
}
| _ => "Unexpected error occurred"
}
Error(msg)
})
}
}
module Product = {
type t = {id: int, name: string}
@val @scope("globalThis")
external fetch: (string, 'params) => Promise.t<Response.t<{"data": Js.Nullable.t<array<t>>}>> =
"fetch"
let getProducts = (~token: string, ()) => {
open Promise
let params = {
"Authorization": `Bearer ${token}`,
}
fetch("https://reqres.in/api/products", params)
->then(res => {
res->Response.json
})
->map(data => {
let ret = switch Js.Nullable.toOption(data["data"]) {
| Some(data) => data
| None => []
}
Ok(ret)
})
->catch(e => {
let msg = switch e {
| JsError(err) =>
switch Js.Exn.message(err) {
| Some(msg) => msg
| None => ""
}
| _ => "Unexpected error occurred"
}
Error(msg)
})
}
}
exception FailedRequest(string)
let _ = {
open Promise
Login.login("[email protected]", "pw")
->Promise.then(ret => {
switch ret {
| Ok(token) =>
Js.log("Login successful! Querying data...")
Product.getProducts(~token, ())
| Error(msg) => reject(FailedRequest("Login error - " ++ msg))
}
})
->map(result => {
switch result {
| Ok(products) =>
Js.log("\nAvailable Products:\n---")
Belt.Array.forEach(products, p => {
Js.log(`${Belt.Int.toString(p.id)} - ${p.name}`)
})
| Error(msg) => Js.log("Could not query products: " ++ msg)
}
})
->catch(e => {
switch e {
| FailedRequest(msg) => Js.log("Operation failed! " ++ msg)
| _ => Js.log("Unknown error")
}
})
}