javascriptasynchronouspromiseocamljs-of-ocaml

Is it possible to wrap an asynchronous JS function and use it in OCaml?


We could use js_of_ocaml to wrap a JS function and thus call it within OCaml. I cannot make a working example when the JS function is asynchronous (ie, includes promises and takes time).

The asynchronous JS function JSfun I want to wrap is as follows. The variable x is set to "here" after 2 seconds, and this is the value I want to return.

function JSfun() {
  var x = "before";
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      append("inside setTimeout");
      x = "here";
      resolve(x);
    }, 2000);
  })
}

We could successfully call JSfun in JS and get "runJS here" as expected:

function runJS() {
  JSfun().then(function(res) {
    append("runJS " + res)
  })
}

However, it is hard to imitate this chaining by OCaml. To wrap JSfun in OCaml, it seems that we have to use:

Js.Unsafe.global##.OCamlcall := Js.wrap_callback
    (fun _ ->
       let m = Js.Unsafe.fun_call (Js.Unsafe.js_expr "JSfun") [||] in
       Js.string ((Js.to_string m) ^ " Via OCaml")
    );

And I don't have other idea than calling like this:

function runOCaml() {
  var res = OCamlcall("abc");
  append(res);
}

Unsurprisingly, it does not work: we do see "inside setTimeout" printed, which proves JSfun has been called, but the return value was not there.

Here is the jsfiddle. I also make a working example of wrapping synchronous JS function. In OCaml, the wrapping is:

Js.Unsafe.global##.OCamlcallSync := Js.wrap_callback
    (fun _ ->
       let m = Js.Unsafe.fun_call (Js.Unsafe.js_expr "JSfunSync") [||] in
       Js.string ((Js.to_string m) ^ " Via OCaml")
    );

So does anyone have a solution, an idea or a workaround?


Solution

  • If your js function is asynchronous, your OCaml counterpart should also be async, i.e. it should use Lwt or Async. So, for Lwt you would probably use Lwt.task to create sleeping thread and wakener, then pass callback with wakener to Promises' .then method, and return sleeping thread from your function.

    The code would look like this (some types are probably too general):

    class type promise = 
       object
         method then_ : ('a -> 'b) callback -> 'b meth 
       end
    
    let ocamlcall () = 
      let t, w = Lwt.task () in
      let wakeup = Js.wrap_callback (fun r -> Lwt.wakeup t r) in 
      let (promise : promise Js.t) = Js.Unsafe.fun_call jsfunc [||] in
      let () = ignore @@ promise##then_ wakeup in
      t
    

    Note: I haven't tested this code, but it should give your general idea how to interact with async js functions.

    You can also want to check how jsoo itself works with jsonp, for example: https://github.com/ocsigen/js_of_ocaml/blob/master/lib/jsonp.ml