Skip to content

Latest commit

 

History

History
314 lines (254 loc) · 7.54 KB

exception.mdx

File metadata and controls

314 lines (254 loc) · 7.54 KB
title description canonical
Exception
Exceptions and exception handling in ReScript
/docs/manual/latest/exception

Exception

Exceptions are just a special kind of variant, thrown in exceptional cases (don't abuse them!).

Usage

<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()
};

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:

<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 a JS Exception

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!"
}

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!

<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.

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

<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...