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"]}>
letgetItem= (items) =>ifcallSomeFunctionThatThrows() { // return the found item here1 } else { raise(Not_found) } letresult=try { getItem([1, 2, 3]) } catch { | Not_found=>0// Default value if getItem throws }
functiongetItem(items){if(callSomeFunctionThatThrows()){return1;}throw{RE_EXN_ID: "Not_found",Error: newError()};}varresult;try{result=getItem([1,2,3]);}catch(raw_exn){varexn=Caml_js_exceptions.internalToOCamlException(raw_exn);if(exn.RE_EXN_ID==="Not_found"){result=0;}else{throwexn;}}
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"]}>
switchList.find(i=>i===theItem, myItems) { | item=>Js.log(item) | exceptionNot_found=>Js.log("No such item found!") }
varexit=0;varitem;try{item=List.find(function(i){returni===theItem;},myItems);exit=1;}catch(raw_exn){varexn=Caml_js_exceptions.internalToOCamlException(raw_exn);if(exn.RE_EXN_ID==="Not_found"){console.log("No such item found!");}else{throwexn;}}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"]}>
exceptionInputClosed(string) // later onraise(InputClosed("The stream has closed!"))
varCaml_exceptions=require("./stdlib/caml_exceptions.js");varInputClosed=Caml_exceptions.create("MyFile.InputClosed");throw{RE_EXN_ID: InputClosed,_1: "The stream has closed!",Error: newError()};
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={["ReScript", "JS Output"]}>
try { someJSFunctionThatThrows() } catch { | Js.Exn.Error(obj) =>switchJs.Exn.message(obj) { | Some(m) =>Js.log("Caught a JS exception! Message: "++m) | None=> () } }
varJs_exn=require("./stdlib/js_exn.js");varCaml_js_exceptions=require("./stdlib/caml_js_exceptions.js");try{someJSFunctionThatThrows();}catch(raw_obj){varobj=Caml_js_exceptions.internalToOCamlException(raw_obj);if(obj.RE_EXN_ID===Js_exn.$$Error){varm=obj._1.message;if(m!==undefined){console.log("Caught a JS exception! Message: "+m);}}else{throwobj;}}
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={["ReScript", "JS Output"]}>
letmyTest= () => { Js.Exn.raiseError("Hello!") }
varJs_exn=require("./stdlib/js_exn.js");functionmyTest(){returnJs_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"]}>
exceptionBadArgument({myMessage: string}) letmyTest= () => { raise(BadArgument({myMessage: "Oops!"})) }
varCaml_exceptions=require("./stdlib/caml_exceptions.js");varBadArgument=Caml_exceptions.create("Playground.BadArgument");functionmyTest(){throw{RE_EXN_ID: BadArgument,myMessage: "Oops!",Error: newError()};}
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={["ReScript", "JS Output"]}>
try { someOtherJSFunctionThatThrows() } catch { | Not_found=>...// catch a ReScript exception | Invalid_argument(_) =>...// catch a second ReScript exception | Js.Exn.Error(obj) =>...// catch the JS exception }
varJs_exn=require("./stdlib/js_exn.js");varCaml_js_exceptions=require("./stdlib/caml_js_exceptions.js");try{someOtherJSFunctionThatThrows();}catch(raw_obj){varobj=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){throwobj;}}
This technically works, but hopefully you don't ever have to work with such code...