{0 Error handling between OCaml and JavaScript}

This page explains how exceptions propagate between OCaml and JavaScript code,
and how to properly handle errors in both directions.

{1:catching-js-exceptions Catching JavaScript exceptions in OCaml}

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]:

{@ocaml[
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);
    None
]}

{2 Inspecting JavaScript errors}

The {{!Js_of_ocaml.Js.Js_error}Js_error} module provides functions to inspect error details:

{@ocaml[
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") stack
]}

{1:raising-js-errors Raising JavaScript errors from OCaml}

To throw a JavaScript error that JavaScript code can catch:

{@ocaml[
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
    n
]}


{1:exception-propagation Exception propagation in exported functions}

When you export OCaml functions to JavaScript, uncaught OCaml exceptions will
propagate to JavaScript. However, OCaml exceptions are {b not} JavaScript
[Error] objects (see {{!page-"runtime-representation".exceptions}runtime representation}).

{@ocaml[
let divide x y =
  if y = 0 then failwith "Division by zero"
  else x / y

let () = Js.export "divide" divide
]}

From JavaScript, you can catch the exception, but it won't be an [Error]:
{@javascript[
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

{1:stack_traces Stack traces for 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.

{2 Manual attachment}

Use {{!Js_of_ocaml.Js.Js_error.attach_js_backtrace}Js_error.attach_js_backtrace} to attach a JavaScript stack trace
to an OCaml exception:

{@ocaml[
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 exn
]}

The [~force:false] parameter only attaches a new error if one isn't already
present. Use [~force:true] to always attach a fresh stack trace.

{2 Extracting attached errors}

Use {{!Js_of_ocaml.Js.Js_error.of_exn}Js_error.of_exn} to extract the JavaScript error from an OCaml
exception:

{@ocaml[
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)
]}

{2 Automatic attachment}

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:

{ol
{li [Printexc.backtrace_status() = true], {b and}}
{li Either:
 {ul
 {li The environment variable [OCAMLRUNPARAM] contains [b=1], or}
 {li The program was compiled with [--enable with-js-error]}}}}

Example with environment variable:
{v
OCAMLRUNPARAM=b=1 node program.js
v}

Example with compiler flag:
{v
js_of_ocaml --enable with-js-error program.byte
v}

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.
