This page explains how exceptions propagate between OCaml and JavaScript code, and how to properly handle errors in both directions.
When JavaScript code throws an exception and it propagates to OCaml code, it is wrapped in the Js_error.Exn exception, which holds a value of type Js_error.t:
open Js_of_ocaml
let safe_parse json_str =
try
Some (Js._JSON##parse (Js.string json_str))
with Js_error.Exn err ->
(* err has type Js_error.t *)
Printf.eprintf "Parse error: %s\n" (Js_error.to_string err);
NoneThe Js_error module provides functions to inspect error details:
let handle_error err =
(* Error message *)
let msg = Js_error.message err in
(* Error name (e.g., "TypeError", "ReferenceError") *)
let name = Js_error.name err in
(* Stack trace (if available) *)
let stack = Js_error.stack err in
Printf.eprintf "Error [%s]: %s\n" name msg;
Option.iter (Printf.eprintf "Stack:\n%s\n") stackTo throw a JavaScript error that JavaScript code can catch:
let validate_positive n =
if n < 0 then
let err = new%js Js.error_constr (Js.string "Value must be positive") in
Js_error.raise_ (Js_error.of_error err)
else
nWhen you export OCaml functions to JavaScript, uncaught OCaml exceptions will propagate to JavaScript. However, OCaml exceptions are not JavaScript Error objects (see runtime representation).
let divide x y =
if y = 0 then failwith "Division by zero"
else x / y
let () = Js.export "divide" divideFrom JavaScript, you can catch the exception, but it won't be an Error:
try {
divide(10, 0);
} catch (e) {
// e is an array, not an Error object
// e.message is undefined
console.log(e); // [0, [0, "Failure", ...], "Division by zero"]
}For better JavaScript interoperability, raise JavaScript errors instead of OCaml exceptions
By default, OCaml exceptions don't carry JavaScript stack traces. This makes debugging difficult since you can't see where an exception originated in browser DevTools or Node.js.
The solution is to attach a JavaScript Error object to an OCaml exception. JavaScript Error objects capture the call stack at the point of creation, so attaching one to an OCaml exception preserves the stack trace for debugging.
Use Js_error.attach_js_backtrace to attach a JavaScript stack trace to an OCaml exception:
let process data =
try
do_something data
with exn ->
let exn = Js_error.attach_js_backtrace exn ~force:false in
(* Log or rethrow with JS backtrace attached *)
raise exnThe ~force:false parameter only attaches a new error if one isn't already present. Use ~force:true to always attach a fresh stack trace.
Use Js_error.of_exn to extract the JavaScript error from an OCaml exception:
let log_with_backtrace exn =
match Js_error.of_exn exn with
| Some js_err ->
Printf.eprintf "JS Error: %s\n" (Js_error.to_string js_err);
Option.iter (Printf.eprintf "Stack:\n%s\n") (Js_error.stack js_err)
| None ->
Printf.eprintf "OCaml Error: %s\n" (Printexc.to_string exn)Js_of_ocaml can automatically attach a JavaScript Error whenever an OCaml exception is raised. This is convenient but has a performance cost since creating Error objects is expensive.
Automatic attachment is enabled when:
Printexc.backtrace_status() = true, andEither:
OCAMLRUNPARAM contains b=1, or--enable with-js-errorExample with environment variable:
OCAMLRUNPARAM=b=1 node program.js
Example with compiler flag:
js_of_ocaml --enable with-js-error program.byte
When an unhandled OCaml exception has an attached JavaScript Error, it will be thrown as that error, allowing browsers and Node.js to display a proper stack trace.