What is the proper type for node callback?


#1

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?


#2

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