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={["Reason (Old Syntax)", "ML (Older Syntax)", "JS Output"]}>
let getItem = (items) =>
if (callSomeFunctionThatThrows()) {
// return the found item here
1;
} else {
raise(Not_found);
};
let result =
try (getItem([|1, 2, 3|])) {
| Not_found => 0 /* Default value if getItem throws */
};
let getItem items =
if callSomeFunctionThatThrows () then
(* return the found item here *)
1
else
raise Not_found
let result =
try getItem [|1; 2; 3|] with
| Not_found -> 0 (* Default value if getItem throws *)
function getItem(items) {
if (callSomeFunctionThatThrows()) {
return 1;
}
throw {
RE_EXN_ID: "Not_found",
Error: new Error()
};
}
var result;
try {
result = getItem([1, 2, 3]);
} 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={["Reason (Old Syntax)", "ML (Older Syntax)", "JS Output"]}>
switch (List.find(i => i === theItem, myItems)) {
| item => Js.log(item)
| exception Not_found => Js.log("No such item found!")
};
let () = match List.find (fun i -> i == theItem) myItems with
| item -> Js.log item
| exception Not_found ->
Js.log "No such item found!"
var exit = 0;
var item;
try {
item = List.find(function(i) {
return i === theItem;
}, myItems);
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={["Reason (Old Syntax)", "ML (Older Syntax)", "JS Output"]}>
exception InputClosed(string);
// later on
raise(InputClosed("The stream has closed!"));
exception InputClosed of 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 Js.Exn.Error(payload)
variant. To catch an exception thrown from the JS side:
<CodeTab labels={["Reason (Old Syntax)", "ML (Older Syntax)", "JS Output"]}>
try (someJSFunctionThatThrows()) {
| Js.Exn.Error(obj) =>
switch (Js.Exn.message(obj)) {
| Some(m) => Js.log("Caught a JS exception! Message: " ++ m)
| None => ()
}
};
let () = try someJSFunctionThatThrows () with
| Js.Exn.Error obj ->
match Js.Exn.message obj with
| Some m -> Js.log ("Caught a JS exception! Message: " ^ m)
| None -> ()
var Js_exn = require("./stdlib/js_exn.js");
var Caml_js_exceptions = require("./stdlib/caml_js_exceptions.js");
try {
someJSFunctionThatThrows();
} catch (raw_obj) {
var obj = Caml_js_exceptions.internalToOCamlException(raw_obj);
if (obj.RE_EXN_ID === Js_exn.$$Error) {
var m = obj._1.message;
if (m !== undefined) {
console.log("Caught a JS exception! Message: " + m);
}
} else {
throw obj;
}
}
The obj
here is of type Js.Exn.t
, intentionally opaque to disallow illegal operations. To operate on obj
, do like the code above by using the standard library's Js.Exn
module's helpers.
raise(MyException)
raises a ReScript exception. To raise a JavaScript exception (whatever your purpose is), use Js.Exn.raiseError
:
<CodeTab labels={["Reason (Old Syntax)", "ML (Older Syntax)", "JS Output"]}>
let myTest = () => {
Js.Exn.raiseError("Hello!");
};
let myTest () =
Js.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={["Reason (Old Syntax)", "ML (Older Syntax)", "JS Output"]}>
exception BadArgument({myMessage: string});
let myTest = () => {
raise(BadArgument({myMessage: "Oops!"}));
};
exception BadArgument of {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.
<CodeTab labels={["Reason (Old Syntax)", "ML (Older Syntax)", "JS Output"]}>
try (someOtherJSFunctionThatThrows()) {
| Not_found => ... // catch a ReScript exception
| Invalid_argument(_) => ... // catch a second ReScript exception
| Js.Exn.Error(obj) => ... // catch the JS exception
};
try someOtherJSFunctionThatThrows() with
| Not_found -> ... (* catch a ReScript exception *)
| Invalid_argument _ -> ... (* catch a second ReScript exception *)
| Js.Exn.Error obj -> ... (* catch the JS exception *)
var Js_exn = require("./stdlib/js_exn.js");
var Caml_js_exceptions = require("./stdlib/caml_js_exceptions.js");
try {
someOtherJSFunctionThatThrows();
} catch (raw_obj) {
var obj = Caml_js_exceptions.internalToOCamlException(raw_obj);
if (
obj.RE_EXN_ID !== "Not_found" &&
obj.RE_EXN_ID !== "Invalid_argument" &&
obj.RE_EXN_ID !== Js_exn.$$Error
) {
throw obj;
}
}
This technically works, but hopefully you don't ever have to work with such code...