Exception
Exceptions are just a special kind of variant, thrown in exceptional cases (don't abuse them!).
Usage
let getItem = (items) =>
if callSomeFunctionThatThrows() {
// return the found item here
1
} else {
raise(Not_found)
}
let result =
try {
getItem([1, 2, 3])
} catch {
| Not_found => 0 // Default value if getItem throws
}
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:
switch List.find(i => i === theItem, myItems) {
| item => Js.log(item)
| exception Not_found => Js.log("No such item found!")
}
You can also make your own exceptions like you'd make a variant (exceptions need to be capitalized too).
exception InputClosed(string)
// later on
raise(InputClosed("The stream has closed!"))
Catching JS Exceptions
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:
try {
someJSFunctionThatThrows()
} catch {
| Js.Exn.Error(obj) =>
switch Js.Exn.message(obj) {
| Some(m) => Js.log("Caught a JS exception! Message: " ++ m)
| None => ()
}
}
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 a JS Exception
raise(MyException)
raises a ReScript exception. To raise a JavaScript exception (whatever your purpose is), use Js.Exn.raiseError
:
let myTest = () => {
Js.Exn.raiseError("Hello!")
}
Then you can catch it from the JS side:
JS// after importing `myTest`...
try {
myTest()
} catch (e) {
console.log(e.message) // "Hello!"
}
Catch ReScript Exceptions from JS
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!
exception BadArgument({myMessage: string})
let myTest = () => {
raise(BadArgument({myMessage: "Oops!"}))
}
Then, in your JS:
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.
Tips & Tricks
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.
Catch Both ReScript and JS Exceptions in the Same catch
Clause
try {
someOtherJSFunctionThatThrows()
} catch {
| Not_found => ... // catch a ReScript exception
| Invalid_argument(_) => ... // catch a second ReScript exception
| Js.Exn.Error(obj) => ... // catch the JS exception
}
This technically works, but hopefully you don't ever have to work with such code...