What is the proper type for node callback?


I was wondering how to define a type for node callback to use with external node functions. Starting from:

type nodeCallback = (Js.Nullable.t('e), Js.Nullable.t('a)) => unit;

Using error type 'e does surely make it compatible with anything imaginable, but it becomes a problem if I try to convert it into a Belt.Result.t('a, exn) (Because using exn in Result is both logical and convenient.). I can also trust that a proper node function will return an Error object (unlike some random js libs) so i am tempted to write:

type nodeCallback = (Js.Nullable.t(exn), Js.Nullable.t('a)) => unit;

However it turns out that:

  • exn type must always contain an ocaml exception (affects its shape in js side too)
  • Reason transpiling makes sure that js Error:s thrown inside reason try(…) expressions are magically converted into ocaml exceptions by processing them through Caml_js_exceptions.internalToOCamlException
  • Js.Exn.t is the type of these magically converted (js Error -> ocaml exn) errors. exn | Js.Exn.Error(Js.Exn.t) to be exact.
  • Using exn in nodeCallback type and then using nodeCallback type in some [@bs...] external xyz: ...=> nodeCallback => unit = "" declaration does not put the Error from the callback through the conversion function to become proper ocaml exn.

Is it a design decision that an explicit Js.Exn.Error constructor is not provided? Perhaps handling node callbacks could be done in some different way… Any ideas what would be the proper approach here?


I’d model the callback type as:

type nodeCallback('a) = (. Js.nullable(Js.Exn.t), Js.nullable('a)) => unit;

Few things to note:

In general to handle JavaScript exceptions in a safe way use the Js.Exn module, it provides useful functions to work with them. You can use Belt.Result.t('a, Js.Exn.t).