title | description | canonical |
---|---|---|
Exception |
Exceptions and exception handling in ReScript |
/docs/manual/latest/exception |
Exceptions are just a special kind of variant, thrown in exceptional cases (don't abuse them!).
<CodeTab labels={["ReScript", "JS Output"]}>
let getItem = (item: int) =>
if (item === 3) {
// return the found item here
1
} else {
raise(Not_found)
}
let result =
try {
getItem(2)
} catch {
| Not_found => 0 // Default value if getItem throws
}
var Caml_js_exceptions = require("./stdlib/caml_js_exceptions.js");
function getItem(item) {
if (item === 3) {
return 1;
}
throw {
RE_EXN_ID: "Not_found",
Error: new Error()
};
}
var result;
try {
result = getItem(2);
}
catch (raw_exn){
var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
if (exn.RE_EXN_ID === "Not_found") {
result = 0;
} else {
throw exn;
}
}
Note that the above is just for demonstration purposes; in reality, you'd return an option<int>
directly from getItem
and avoid the try
altogether.
You can directly match on exceptions while getting another return value from a function:
<CodeTab labels={["ReScript", "JS Output"]}>
switch list{1, 2, 3}->List.getExn(4) {
| item => Console.log(item)
| exception Not_found => Console.log("No such item found!")
}
var List = require("./stdlib/list.js");
var Caml_js_exceptions = require("./stdlib/caml_js_exceptions.js");
var exit = 0;
var item;
try {
item = List.find((function (i) {
return i === 4;
}), {
hd: 1,
tl: {
hd: 2,
tl: {
hd: 3,
tl: /* [] */0
}
}
});
exit = 1;
}
catch (raw_exn){
var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
if (exn.RE_EXN_ID === "Not_found") {
console.log("No such item found!");
} else {
throw exn;
}
}
if (exit === 1) {
console.log(item);
}
You can also make your own exceptions like you'd make a variant (exceptions need to be capitalized too).
<CodeTab labels={["ReScript", "JS Output"]}>
exception InputClosed(string)
// later on
raise(InputClosed("The stream has closed!"))
var Caml_exceptions = require("./stdlib/caml_exceptions.js");
var InputClosed = Caml_exceptions.create("MyFile.InputClosed");
throw {
RE_EXN_ID: InputClosed,
_1: "The stream has closed!",
Error: new Error()
};
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:
Throw an exception from JS:
// Example.js
exports.someJsFunctionThatThrows = () => {
throw new Error("A Glitch in the Matrix!");
}
Then catch it from ReScript:
// import the method in Example.js
@module("./Example")
external someJsFunctionThatThrows: () => unit = "someJsFunctionThatThrows"
try {
// call the external method
someJSFunctionThatThrows()
} catch {
| Exn.Error(obj) =>
switch Exn.message(obj) {
| Some(m) => Console.log("Caught a JS exception! Message: " ++ m)
| None => ()
}
}
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
module's helpers.
raise(MyException)
raises a ReScript exception. To raise a JavaScript exception (whatever your purpose is), use Exn.raiseError
:
<CodeTab labels={["ReScript", "JS Output"]}>
let myTest = () => {
Exn.raiseError("Hello!")
}
var Js_exn = require("./stdlib/js_exn.js");
function myTest() {
return Js_exn.raiseError("Hello!");
}
Then you can catch it from the JS side:
// after importing `myTest`...
try {
myTest()
} catch (e) {
console.log(e.message) // "Hello!"
}
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!
<CodeTab labels={["ReScript", "JS Output"]}>
exception BadArgument({myMessage: string})
let myTest = () => {
raise(BadArgument({myMessage: "Oops!"}))
}
var Caml_exceptions = require("./stdlib/caml_exceptions.js");
var BadArgument = Caml_exceptions.create("Playground.BadArgument");
function myTest() {
throw {
RE_EXN_ID: BadArgument,
myMessage: "Oops!",
Error: new Error()
};
}
Then, in your JS:
// after importing `myTest`...
try {
myTest()
} catch (e) {
console.log(e.myMessage) // "Oops!"
console.log(e.Error.stack) // the stack trace
}
Note:
RE_EXN_ID
is an internal field for bookkeeping purposes. Don't use it on the JS side. Use the other fields.
The above BadArgument
exception takes an inline record type. We special-case compile the exception as {RE_EXN_ID, myMessage, Error}
for good ergonomics. If the exception instead took ordinary positional arguments, l like the standard library's Invalid_argument("Oops!")
, which takes a single argument, the argument is compiled to JS as the field _1
instead. A second positional argument would compile to _2
, etc.
When you have ordinary variants, you often don't need exceptions. For example, instead of throwing when item
can't be found in a collection, try to return an option<item>
(None
in this case) instead.
try {
someOtherJSFunctionThatThrows()
} catch {
| Not_found => ... // catch a ReScript exception
| Invalid_argument(_) => ... // catch a second ReScript exception
| Exn.Error(obj) => ... // catch the JS exception
}
This technically works, but hopefully you don't ever have to work with such code...