Is there an Js_of_ocaml equiv of Brr.Fut.t ?
I have found Promise (promise_jsoo.Promise) and async_js/example.ml at master · janestreet/async_js · GitHub
but its not clear to me which way to go.
Taking a step back, I just want to write async style code in OCaml, with some ppx syntax for let%... =
Offtopic comment I’m not entitled to since I’ve never used either of the syntaxes with async features, but OCaml now has a built-in syntax for monadic-style let bindings.
1 Like
I think it depends a little on what async things you want to do and what are your constraints. Note, promise_jsoo and Brr.Fut.t both have ‘syntaxes’ for monadic-style let bindings. I think the js_of_ocaml stance is to go with promise_jsoo (at least that’s what I inferred from How to tackle a JavaScript function that returns a JS `Promise`? · Issue #1073 · ocsigen/js_of_ocaml · GitHub) but that’s for wrapping JS promises in a monadic interface, you might still need Async (or Lwt).
Where’s your “async-ness” coming from ? A web API returning a promise (e.g. fetch() global function - Web APIs | MDN), waiting for an event (e.g. websocket receive) or imposed by an OCaml library that is built using Async (or Lwt) ?
1 Like
Two sources: 1. Fetch 2. Converting onMessage to a chan.
fetch
This is standard Fetch API. Nothing fancy.
let response_to_txt (r : Brr_io.Fetch.Response.t) : Jstr.t Fut.t =
let open Fut.Syntax in
let* txt = Brr_io.Fetch.Body.text @@ Brr_io.Fetch.Response.as_body r in
Fut.return @@ ok_or_throw txt "response_to_txt"
let response_to_ab (r : Brr_io.Fetch.Response.t) : Brr.Tarray.Buffer.t Fut.t =
let open Fut.Syntax in
let* blob = Brr_io.Fetch.Body.blob @@ Brr_io.Fetch.Response.as_body r in
let blob = ok_or_throw blob "response_to_ab (to blob step)" in
let* ab = Brr.Blob.array_buffer blob in
Fut.return @@ ok_or_throw ab "response_to-ab (to arraybuffer step)"
onMessage → chan
The “onMessage” handler has inversion of control / we need to write a state machine. Instead, we build a chan primitive, where onMessagre pushes into the chan (queue) and some other process takes items off the queue in normal code.
To do this, we have a queue of “values” and a queue of “promises” (code waiting for a value) and match them up. This allows us to write code that deals with onMessage handler in an 'async style"
open B_brr
open Core
type 'a inner = Empty | Fns of ('a -> unit) * ('a -> unit) Queue.t | Elems of 'a * 'a Queue.t
type 'a t = 'a inner ref
let create () : 'a t = ref @@ Empty
let push_elem (c : 'a t) (e : 'a) =
match !c with
| Empty -> c := Elems (e, Queue.create ())
| Fns (x, qx) ->
(match Queue.dequeue qx with
| Some x' -> c := Fns (x', qx)
| None -> c := Empty);
x e
| Elems (_x, qx) -> Queue.enqueue qx e
let push_fn (c : 'a t) (f : 'a -> unit) =
match !c with
| Empty -> c := Fns (f, Queue.create ())
| Fns (_x, qx) -> Queue.enqueue qx f
| Elems (x, qx) ->
(match Queue.dequeue qx with
| Some x' -> c := Elems (x', qx)
| None -> c := Empty);
f x
let wait_on (c : 'a t) =
let p, r = Fut.create () in
push_fn c r;
p
let rec drop_until (c : 'a t) (f : 'a -> bool) =
let open Fut.Syntax in
let p, r = Fut.create () in
push_fn c r;
let* t = p in
if f t then Fut.return t else drop_until c f
let raw_data_appender (chan : Jv.t t) (ev : Brr_io.Message.Ev.t Brr.Ev.t) =
let t : Brr_io.Message.Ev.t = Brr.Ev.as_type ev in
let data : Jv.t = Brr_io.Message.Ev.data t in
push_elem chan data
let data_appender (chan : 'a t) ~(f : Jv.t -> 'a) (ev : Brr_io.Message.Ev.t Brr.Ev.t) =
let t : Brr_io.Message.Ev.t = Brr.Ev.as_type ev in
let data : Jv.t = Brr_io.Message.Ev.data t in
let e : 'a = f data in
push_elem chan e
let rec loop_forever (chan : 'a t) ~(f : 'a -> unit) =
let open Fut.Syntax in
let* x = wait_on chan in
let _ = f x in
loop_forever chan ~f
let pipe_to_chan (chan : 'a t) (target : Brr.Ev.target) ~(f : Jv.t -> 'a) =
Brr.Ev.listen Brr_io.Message.Ev.message (data_appender chan ~f) target
let rec select (chan : 'a t) ~(f : 'a -> 'b option) : 'b Fut.t =
let open Fut.Syntax in
let* (msg : 'a) = wait_on chan in
match f msg with
| None -> select chan ~f
| Some v -> Fut.return v
let create_from_port (p : Brr_io.Message.Port.t) ~(f : Jv.t -> 'a) : 'a t =
let c = create () in
let _ = pipe_to_chan c Brr_io.Message.(Port.as_target p) ~f in
let _ = Brr_io.Message.(Port.start p) in
edit
From https://github.com/ocsigen/js_of_ocaml/blob/master/examples/webgl/webgldemo.ml
let http_get url =
XmlHttpRequest.get url
>>= fun r ->
let cod = r.XmlHttpRequest.code in
let msg = r.XmlHttpRequest.content in
if cod = 0 || cod = 200 then Lwt.return msg else fst (Lwt.wait ())
the impression I get is to go with Lwt.t ?
It is common to use Lwt inside a program compiled with js_of_ocaml. The two projects have shared authors and maintainers for a while, so they have evolved to work well together.
2 Likes