Skip to content

Commit e18406c

Browse files
authoredJun 4, 2025··
Document the new exception API (#1046)
* Document the new API around exceptions * update other pages with the new exception API * improve wording
1 parent 3ad8c92 commit e18406c

File tree

6 files changed

+146
-130
lines changed

6 files changed

+146
-130
lines changed
 

‎pages/docs/manual/v12.0.0/async-await.mdx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -130,15 +130,15 @@ You may use `try / catch` or `switch` to handle exceptions during async executio
130130
```res
131131
// For simulation purposes
132132
let authenticate = async () => {
133-
raise(Exn.raiseRangeError("Authentication failed."))
133+
JsError.RangeError.throwWithMessage("Authentication failed.")
134134
}
135135
136136
let checkAuth = async () => {
137137
try {
138138
await authenticate()
139139
} catch {
140-
| Exn.Error(e) =>
141-
switch Exn.message(e) {
140+
| JsExn(e) =>
141+
switch JsExn.message(e) {
142142
| Some(msg) => Console.log("JS error thrown: " ++ msg)
143143
| None => Console.log("Some other exception has been thrown")
144144
}
@@ -152,14 +152,14 @@ You may unify error and value handling in a single switch as well:
152152

153153
```res
154154
let authenticate = async () => {
155-
raise(Exn.raiseRangeError("Authentication failed."))
155+
JsError.RangeError.throwWithMessage("Authentication failed.")
156156
}
157157
158158
let checkAuth = async () => {
159159
switch await authenticate() {
160160
| _ => Console.log("ok")
161-
| exception Exn.Error(e) =>
162-
switch Exn.message(e) {
161+
| exception JsExn(e) =>
162+
switch JsExn.message(e) {
163163
| Some(msg) => Console.log("JS error thrown: " ++ msg)
164164
| None => Console.log("Some other exception has been thrown")
165165
}

‎pages/docs/manual/v12.0.0/exception.mdx

Lines changed: 134 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,18 @@ You can create your own exceptions like you'd make a variant (exceptions need to
1515
```res example
1616
exception InputClosed(string)
1717
// later on
18-
raise(InputClosed("The stream has closed!"))
18+
throw(InputClosed("The stream has closed!"))
1919
```
2020
```js
21-
import * as Caml_exceptions from "./stdlib/caml_exceptions.js";
21+
import * as Primitive_exceptions from "./stdlib/Primitive_exceptions.js";
2222

23-
var InputClosed = /* @__PURE__ */Caml_exceptions.create("Playground.InputClosed");
23+
let InputClosed = /* @__PURE__ */Primitive_exceptions.create("Playground.InputClosed");
2424

2525
throw {
26-
RE_EXN_ID: InputClosed,
27-
_1: "The stream has closed!",
28-
Error: new Error()
29-
};
26+
RE_EXN_ID: InputClosed,
27+
_1: "The stream has closed!",
28+
Error: new Error()
29+
};
3030
```
3131

3232
</CodeTab>
@@ -45,7 +45,7 @@ let getItem = (item: int) =>
4545
// return the found item here
4646
1
4747
} else {
48-
raise(Not_found)
48+
throw(Not_found)
4949
}
5050
5151
let result =
@@ -56,25 +56,24 @@ let result =
5656
}
5757
```
5858
```js
59-
import * as Caml_js_exceptions from "./stdlib/caml_js_exceptions.js";
59+
import * as Primitive_exceptions from "./stdlib/Primitive_exceptions.js";
6060

6161
function getItem(item) {
6262
if (item === 3) {
6363
return 1;
6464
}
6565
throw {
66-
RE_EXN_ID: "Not_found",
67-
Error: new Error()
68-
};
66+
RE_EXN_ID: "Not_found",
67+
Error: new Error()
68+
};
6969
}
7070

71-
var result;
71+
let result;
7272

7373
try {
7474
result = getItem(2);
75-
}
76-
catch (raw_exn){
77-
var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
75+
} catch (raw_exn) {
76+
let exn = Primitive_exceptions.internalToException(raw_exn);
7877
if (exn.RE_EXN_ID === "Not_found") {
7978
result = 0;
8079
} else {
@@ -98,28 +97,27 @@ switch list{1, 2, 3}->List.getExn(4) {
9897
}
9998
```
10099
```js
101-
import * as Core__List from "./stdlib/core__List.js";
102-
import * as Caml_js_exceptions from "./stdlib/caml_js_exceptions.js";
100+
import * as Stdlib_List from "./stdlib/Stdlib_List.js";
101+
import * as Primitive_exceptions from "./stdlib/Primitive_exceptions.js";
103102

104-
var exit = 0;
103+
let exit = 0;
105104

106-
var item;
105+
let item;
107106

108107
try {
109-
item = Core__List.getExn({
110-
hd: 1,
111-
tl: {
112-
hd: 2,
113-
tl: {
114-
hd: 3,
115-
tl: /* [] */0
116-
}
117-
}
118-
}, 4);
108+
item = Stdlib_List.getExn({
109+
hd: 1,
110+
tl: {
111+
hd: 2,
112+
tl: {
113+
hd: 3,
114+
tl: /* [] */0
115+
}
116+
}
117+
}, 4);
119118
exit = 1;
120-
}
121-
catch (raw_exn){
122-
var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
119+
} catch (raw_exn) {
120+
let exn = Primitive_exceptions.internalToException(raw_exn);
123121
if (exn.RE_EXN_ID === "Not_found") {
124122
console.log("No such item found!");
125123
} else {
@@ -142,7 +140,7 @@ Used to check if argument is valid. This exception takes a string.
142140
```res example
143141
let divide = (a, b) =>
144142
if b == 0 {
145-
raise(Invalid_argument("Denominator is zero"))
143+
throw(Invalid_argument("Denominator is zero"))
146144
} else {
147145
a / b
148146
}
@@ -154,25 +152,24 @@ try divide(2, 0)->Console.log catch {
154152
```
155153

156154
```js
157-
import * as Caml_int32 from "./stdlib/caml_int32.js";
158-
import * as Caml_js_exceptions from "./stdlib/caml_js_exceptions.js";
155+
import * as Primitive_int from "./stdlib/Primitive_int.js";
156+
import * as Primitive_exceptions from "./stdlib/Primitive_exceptions.js";
159157

160158
function divide(a, b) {
161159
if (b === 0) {
162160
throw {
163-
RE_EXN_ID: "Invalid_argument",
164-
_1: "Denominator is zero",
165-
Error: new Error()
166-
};
161+
RE_EXN_ID: "Invalid_argument",
162+
_1: "Denominator is zero",
163+
Error: new Error()
164+
};
167165
}
168-
return Caml_int32.div(a, b);
166+
return Primitive_int.div(a, b);
169167
}
170168

171169
try {
172170
console.log(divide(2, 0));
173-
}
174-
catch (raw_msg){
175-
var msg = Caml_js_exceptions.internalToOCamlException(raw_msg);
171+
} catch (raw_msg) {
172+
let msg = Primitive_exceptions.internalToException(raw_msg);
176173
if (msg.RE_EXN_ID === "Invalid_argument") {
177174
console.log(msg._1);
178175
} else {
@@ -185,7 +182,7 @@ catch (raw_msg){
185182

186183
### `Assert_failure`
187184

188-
Raise when you use `assert(condition)` and `condition` is false. The arguments
185+
Thrown when you use `assert(condition)` and `condition` is false. The arguments
189186
are the location of the `assert` in the source code (file name, line number, column number).
190187

191188
<CodeTab labels={["ReScript", "JS Output"]}>
@@ -208,55 +205,43 @@ try decodeUser(%raw("{}"))->Console.log catch {
208205
```
209206

210207
```js
211-
mport * as Caml_js_exceptions from "./stdlib/caml_js_exceptions.js";
208+
import * as Primitive_exceptions from "./stdlib/Primitive_exceptions.js";
212209

213210
function decodeUser(json) {
214-
if (!Array.isArray(json) && (json === null || typeof json !== "object") && typeof json !== "number" && typeof json !== "string" && typeof json !== "boolean") {
215-
throw {
216-
RE_EXN_ID: "Assert_failure",
217-
_1: [
218-
"playground.res",
219-
8,
220-
9
221-
],
222-
Error: new Error()
223-
};
224-
}
225-
if (typeof json === "object" && !Array.isArray(json)) {
226-
var match = json["name"];
227-
var match$1 = json["age"];
228-
if (match !== undefined && !(!Array.isArray(match) && (match === null || typeof match !== "object") && typeof match !== "number" && typeof match !== "string" && typeof match !== "boolean") && typeof match === "string" && match$1 !== undefined && !(!Array.isArray(match$1) && (match$1 === null || typeof match$1 !== "object") && typeof match$1 !== "number" && typeof match$1 !== "string" && typeof match$1 !== "boolean") && typeof match$1 === "number") {
211+
if (typeof json === "object" && json !== null && !Array.isArray(json)) {
212+
let match = json["name"];
213+
let match$1 = json["age"];
214+
if (typeof match === "string" && typeof match$1 === "number") {
229215
return [
230-
match,
231-
match$1 | 0
232-
];
216+
match,
217+
match$1 | 0
218+
];
233219
}
234220
throw {
235-
RE_EXN_ID: "Assert_failure",
236-
_1: [
237-
"playground.res",
238-
6,
239-
11
240-
],
241-
Error: new Error()
242-
};
221+
RE_EXN_ID: "Assert_failure",
222+
_1: [
223+
"playground.res",
224+
6,
225+
11
226+
],
227+
Error: new Error()
228+
};
243229
}
244230
throw {
245-
RE_EXN_ID: "Assert_failure",
246-
_1: [
247-
"playground.res",
248-
8,
249-
9
250-
],
251-
Error: new Error()
252-
};
231+
RE_EXN_ID: "Assert_failure",
232+
_1: [
233+
"playground.res",
234+
8,
235+
9
236+
],
237+
Error: new Error()
238+
};
253239
}
254240

255241
try {
256242
console.log(decodeUser({}));
257-
}
258-
catch (raw_loc){
259-
var loc = Caml_js_exceptions.internalToOCamlException(raw_loc);
243+
} catch (raw_loc) {
244+
let loc = Primitive_exceptions.internalToException(raw_loc);
260245
if (loc.RE_EXN_ID === "Assert_failure") {
261246
console.log(loc._1);
262247
} else {
@@ -269,7 +254,7 @@ catch (raw_loc){
269254

270255
### `Failure`
271256

272-
Exception raised to signal that the given arguments do not make sense. This
257+
Exception thrown to signal that the given arguments do not make sense. This
273258
exception takes a string as an argument.
274259

275260

@@ -279,7 +264,7 @@ let isValidEmail = email => {
279264
let hasAtSign = String.includes(email, "@")
280265
let hasDot = String.includes(email, ".")
281266
if !(hasAtSign && hasDot) {
282-
raise(Failure("Invalid email address"))
267+
throw(Failure("Invalid email address"))
283268
} else {
284269
true
285270
}
@@ -295,28 +280,27 @@ let isValid = try isValidEmail("rescript.org") catch {
295280
```
296281

297282
```js
298-
import * as Caml_js_exceptions from "./stdlib/caml_js_exceptions.js";
283+
import * as Primitive_exceptions from "./stdlib/Primitive_exceptions.js";
299284

300285
function isValidEmail(email) {
301-
var hasAtSign = email.includes("@");
302-
var hasDot = email.includes(".");
286+
let hasAtSign = email.includes("@");
287+
let hasDot = email.includes(".");
303288
if (hasAtSign && hasDot) {
304289
return true;
305290
}
306291
throw {
307-
RE_EXN_ID: "Failure",
308-
_1: "Invalid email address",
309-
Error: new Error()
310-
};
292+
RE_EXN_ID: "Failure",
293+
_1: "Invalid email address",
294+
Error: new Error()
295+
};
311296
}
312297

313-
var isValid;
298+
let isValid;
314299

315300
try {
316301
isValid = isValidEmail("rescript.org");
317-
}
318-
catch (raw_msg){
319-
var msg = Caml_js_exceptions.internalToOCamlException(raw_msg);
302+
} catch (raw_msg) {
303+
let msg = Primitive_exceptions.internalToException(raw_msg);
320304
if (msg.RE_EXN_ID === "Failure") {
321305
console.error(msg._1);
322306
isValid = false;
@@ -330,12 +314,12 @@ catch (raw_msg){
330314

331315
### `Division_by_zero`
332316

333-
Exception raised by integer division and remainder operations when their second argument is zero.
317+
Exception thrown by integer division and remainder operations when their second argument is zero.
334318

335319

336320
<CodeTab labels={["ReScript", "JS Output"]}>
337321
```res example
338-
// ReScript raise `Division_by_zero` if the denominator is zero
322+
// ReScript throws `Division_by_zero` if the denominator is zero
339323
let result = try Some(10 / 0) catch {
340324
| Division_by_zero => None
341325
}
@@ -344,16 +328,15 @@ Console.log(result) // None
344328
```
345329

346330
```js
347-
import * as Caml_int32 from "./stdlib/caml_int32.js";
348-
import * as Caml_js_exceptions from "./stdlib/caml_js_exceptions.js";
331+
import * as Primitive_int from "./stdlib/Primitive_int.js";
332+
import * as Primitive_exceptions from "./stdlib/Primitive_exceptions.js";
349333

350-
var result;
334+
let result;
351335

352336
try {
353-
result = Caml_int32.div(10, 0);
354-
}
355-
catch (raw_exn){
356-
var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
337+
result = Primitive_int.div(10, 0);
338+
} catch (raw_exn) {
339+
let exn = Primitive_exceptions.internalToException(raw_exn);
357340
if (exn.RE_EXN_ID === "Division_by_zero") {
358341
result = undefined;
359342
} else {
@@ -368,7 +351,7 @@ console.log(result);
368351

369352
## Catching JS Exceptions
370353

371-
To distinguish between JavaScript exceptions and ReScript exceptions, ReScript namespaces JS exceptions under the `Exn.Error(payload)` variant. To catch an exception thrown from the JS side:
354+
To distinguish between JavaScript exceptions and ReScript exceptions, ReScript namespaces JS exceptions under the `JsExn(payload)` variant. To catch an exception thrown from the JS side:
372355

373356

374357
Throw an exception from JS:
@@ -392,32 +375,35 @@ try {
392375
// call the external method
393376
someJSFunctionThatThrows()
394377
} catch {
395-
| Exn.Error(obj) =>
396-
switch Exn.message(obj) {
378+
| JsExn(exn) =>
379+
switch JsExn.message(exn) {
397380
| Some(m) => Console.log("Caught a JS exception! Message: " ++ m)
398381
| None => ()
399382
}
400383
}
401384
```
402385

403-
The `obj` here is of type `Exn.t`, intentionally opaque to disallow illegal operations. To operate on `obj`, do like the code above by using the standard library's [`Exn`](api/js/exn) module's helpers.
386+
The payload `exn` here is of type `unknown` since in JS you can throw anything. To operate on `exn`, do like the code above by using the standard library's [`JsExn`](api/core/jsexn) module's helpers
387+
or use [`Type.Classify.classify`](api/core/type/classify#value-classify) to get more information about the runtime type of `exn`.
404388

405-
## Raise a JS Exception
389+
## Throw a JS Exception
406390

407-
`raise(MyException)` raises a ReScript exception. To raise a JavaScript exception (whatever your purpose is), use `Exn.raiseError`:
391+
### Throw a JS Error
392+
393+
`throw(MyException)` throws a ReScript exception. To throw a JavaScript error (whatever your purpose is), use `JsError.throwWithMessage`:
408394

409395
<CodeTab labels={["ReScript", "JS Output"]}>
410396

411397
```res example
412398
let myTest = () => {
413-
Exn.raiseError("Hello!")
399+
JsError.throwWithMessage("Hello!")
414400
}
415401
```
416402
```js
417-
var Js_exn = require("./stdlib/js_exn.js");
403+
import * as Stdlib_JsError from "./stdlib/Stdlib_JsError.js";
418404

419405
function myTest() {
420-
return Js_exn.raiseError("Hello!");
406+
return Stdlib_JsError.throwWithMessage("Hello!");
421407
}
422408
```
423409

@@ -434,6 +420,36 @@ try {
434420
}
435421
```
436422

423+
### Throw a value that is not an JS Error
424+
425+
If you want to throw any value that is not a valid JS Error, use `JsExn.throw`:
426+
427+
<CodeTab labels={["ReScript", "JS Output"]}>
428+
429+
```res example
430+
let myTest = () => {
431+
JsExn.throw("some non-error value!")
432+
}
433+
```
434+
```js
435+
function myTest() {
436+
throw "some non-error value!";
437+
}
438+
```
439+
440+
</CodeTab>
441+
442+
Then you can catch it from the JS side:
443+
444+
```js
445+
// after importing `myTest`...
446+
try {
447+
myTest()
448+
} catch (message) {
449+
console.log(message) // "Hello!"
450+
}
451+
```
452+
437453
## Catch ReScript Exceptions from JS
438454

439455
The previous section is less useful than you think; to let your JS code work with your exception-throwing ReScript code, the latter doesn't actually need to throw a JS exception. ReScript exceptions can be used by JS code!
@@ -444,13 +460,13 @@ The previous section is less useful than you think; to let your JS code work wit
444460
exception BadArgument({myMessage: string})
445461
446462
let myTest = () => {
447-
raise(BadArgument({myMessage: "Oops!"}))
463+
throw(BadArgument({myMessage: "Oops!"}))
448464
}
449465
```
450466
```js
451-
var Caml_exceptions = require("./stdlib/caml_exceptions.js");
467+
import * as Primitive_exceptions from "./stdlib/Primitive_exceptions.js";
452468

453-
var BadArgument = Caml_exceptions.create("Playground.BadArgument");
469+
let BadArgument = /* @__PURE__ */Primitive_exceptions.create("Playground.BadArgument");
454470

455471
function myTest() {
456472
throw {
@@ -491,7 +507,7 @@ try {
491507
} catch {
492508
| Not_found => ... // catch a ReScript exception
493509
| Invalid_argument(_) => ... // catch a second ReScript exception
494-
| Exn.Error(obj) => ... // catch the JS exception
510+
| JsExn(exn) => ... // catch the JS exception
495511
}
496512
```
497513

‎pages/docs/manual/v12.0.0/function.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,7 @@ Both JS exceptions and exceptions defined in ReScript can be caught. The compile
446446
```res example
447447
exception SomeReScriptException
448448
449-
let somethingThatMightThrow = async () => raise(SomeReScriptException)
449+
let somethingThatMightThrow = async () => throw(SomeReScriptException)
450450
451451
let someAsyncFn = async () => {
452452
switch await somethingThatMightThrow() {

‎pages/docs/manual/v12.0.0/lazy-values.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ The first time `Lazy.get` is called, the expensive computation happens and the r
6262

6363
## Exception Handling
6464

65-
For completeness' sake, our files read example might raise an exception because of `readdirSync`. Here's how you'd handle it:
65+
For completeness' sake, our files read example might throw an exception because of `readdirSync`. Here's how you'd handle it:
6666

6767
<CodeTab labels={["ReScript", "JS Output"]}>
6868

‎pages/docs/manual/v12.0.0/overview.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,8 +187,8 @@ canonical: "/docs/manual/v12.0.0/overview"
187187

188188
| JavaScript | ReScript |
189189
| ----------------------------------------- | -------------------------------------------- |
190-
| `throw new SomeError(...)` | `raise(SomeError(...))` |
191-
| `try {a} catch (err) {...} finally {...}` | `try a catch { \| SomeError(err) => ...}` \* |
190+
| `throw new SomeError(...)` | `throw(SomeException(...))` |
191+
| `try {a} catch (err) {...} finally {...}` | `try a catch { \| SomeException(err) => ...}` \* |
192192

193193
\* No finally.
194194

‎pages/docs/manual/v12.0.0/scoped-polymorphic-types.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,9 @@ Scoped polymorphic types work only when they are directly applied to let-binding
9292
```res
9393
exception Abort
9494
95-
let testExn: 'a. unit => 'a = () => raise(Abort) // Works!
95+
let testExn: 'a. unit => 'a = () => throw(Abort) // Works!
9696
97-
let testExn2 = (): 'a. 'a = raise(Abort) // Syntax error!
97+
let testExn2 = (): 'a. 'a = throw(Abort) // Syntax error!
9898
type fn = 'a. 'a => unit // Syntax error!
9999
```
100100

0 commit comments

Comments
 (0)
Please sign in to comment.