How to make a forever/infinite loop of async events in ReScript?

How to make a forever/infinite loop of async events in ReScript without growing heap memory usage?

JS version should look like below:

while (true) {
   await an_async_event()
   // ...
}

I come up with this in ReScript.

let rec loop = () => {
  Promise.resolve("H")->Js.Promise.then_(_ => loop(), _)
}

let _ = loop()

The version above caused out of heap memory.

1 Like

You would do it something like this:

let n_async_event = () => {
  Js.Promise.make((~resolve, ~reject as _) => {
    Js.Global.setTimeout(() => {
      Js.log("So async")
      resolve(. "Much await")
    }, 1000)->ignore
  })
}

let rec forever = () =>
  Js.Promise.resolve()
  |> Js.Promise.then_(_ => n_async_event())
  |> Js.Promise.then_(_ => forever())

Js.log(forever())

2 Likes

What is “Much await” doing here?

 node src/infinite.mjs                                                                         SIGINT(2) ↵  1200  13:31:27 
Promise { <pending> }
So async
So async
So async
So async
So async
So async
So async
So async
So async
So async
So async
So async
So async
So async
So async
So async
So async
...
1 Like

So async represents a side effect, Much await represents the value that the promise returns. If the returned value is required it would be handled in this way:

  Js.Promise.resolve()
  |> Js.Promise.then_(_ =>
    n_async_event() |> Js.Promise.then_(result =>
      Js.log(result) |> Js.Promise.resolve
    )
  )
  |> Js.Promise.then_(_ => forever())
So async
Much await
So async
Much await
So async
Much await
2 Likes

By putting everything together, should it look like this?

let n_async_event = () => {
  Js.Promise.make((~resolve, ~reject as _) => {
    Js.Global.setTimeout(() => {
      Js.log("So async")
      resolve(. "Much await")
    }, 1)->ignore
  })
}

let rec forever = () =>
    Js.Promise.resolve()
    |> Js.Promise.then_(_ =>
      n_async_event() |> Js.Promise.then_(result =>
        Js.log(result) |> Js.Promise.resolve
      )
    )
    |> Js.Promise.then_(_ => forever())

Js.log(forever())

Yep, looks right to me :+1:

1 Like

Thank you. But it doesn’t work. Memory usage keeps growing.

vee      10565 15.4  7.2 830940 286976 ?       Rl+  14:28   7:16 node src/ans1.bs.js                                                                

When it started, the process started using less than 2% of memory. After 48 minutes, it used 7.2% of my computer memory.

I also ran another version written in ClojureScript. It still uses 1.9% of memory until now.

The compiler is unable to do a tail-call optimization here, but you can always resort to a while loop:

let forever = () =>
  while true {
    Js.Promise.resolve()
    |> Js.Promise.then_(_ =>
      n_async_event() |> Js.Promise.then_(result =>
        Js.log(result) |> Js.Promise.resolve
      )
    )
    |> ignore
  }

Playground

1 Like

It took more than 50% of memory in a few seconds, so it doesn’t work.

let n_async_event = () => {
  Js.Promise.make((~resolve, ~reject as _) => {
    Js.Global.setTimeout(() => {
      Js.log("So async")
      resolve(. "Much await")
    }, 1)->ignore
  })
}

let forever = () =>
  while true {
    Js.Promise.resolve()
    |> Js.Promise.then_(_ =>
      n_async_event() |> Js.Promise.then_(result =>
        Js.log(result) |> Js.Promise.resolve
      )
    )
    |> ignore
  }

let _ = forever()

Can you post a full JS example (that does not eat up your memory) for comparison?

It is in ClojureScript. I’m trying to create JS one now.

(ns m1.core
  (:require-macros [cljs.core.async.macros :refer [go-loop]])
  (:require [cljs.core.async :refer [<! >! chan timeout]]))

(let [c (chan 64)]
  (go-loop []
    (let [tm1 (timeout 1)]
      (<! tm1)
      (>! c "Hi"))    
    (recur))
  (go-loop []
    (let [v (<! c)]
      (println v))
    (recur)))

I wrote this in JS a few moments ago.

async function async_task() {
    console.log("Sleep")
    return new Promise(resolve => setTimeout(resolve, 1))
}

(async function() {
    for (;;) {
    	await async_task()
    	console.log("Hi")
    }
})()

Personally, i prefer to use rxjs for that kind of async task. It handles such use case with breeze and elegance.

Does it work with ReScript?

Perfectly fine, I’m using it for all my applications :slight_smile: I’ll show you after my sport session, it’s 5am in France :sweat_smile:

1 Like

Yeah, the problem here is that the JS interpreter knows to handle the for loop differently just because of the async keyword. It’s probably better for this case to rely on a different Promise/Future library.

Also see: How to Use Javascript Promises in a For Loop - Codingem

2 Likes

@veer66

The best advice i can give you is to checkout first the RxJs website and then this great ressource in order to improve your knowledge about RxJS.

Back to your usecase, you want to react to some events from some place. It is the perfect time to use an observable :grinning_face_with_smiling_eyes:

So, in case you’re talking about WebSocket, you can subscribe to new messages this way (from the RxJs docs)

import { webSocket } from "rxjs/webSocket";
const subject = webSocket("ws://localhost:8081");

subject.subscribe(
   msg => console.log('message received: ' + msg), // Called whenever there is a message from the server.
   err => console.log(err), // Called if at any point WebSocket API signals some kind of error.
   () => console.log('complete') // Called when connection is closed (for whatever reason).
 );

And for other cases, you just need to read the docs for the observable principle

Hope it helps :wink:

PS: I’m working on an article about Functional Programming & Clean architecture with React / Redux Toolkit / RxJs in front-end world (in fact i don’t produce Promise anymore, i only use observable from RxJs :sweat_smile:)

1 Like

em…is there no way to write infinite async loop in rescript without using js library or with %raw block embeded? :sweat_smile: @Hongbo

1 Like

Sorry, I discussed this with my colleague @cknitt who suggested that the promise chain needs to be interrupted by calling the recursive function in the setTimeout instead.

This one should work:

let n_async_event = () => {
  Js.Promise.make((~resolve, ~reject as _) => {
    Js.log("So async")
    resolve(. "Much await")
  })
}

let rec forever = () =>
  n_async_event()
  |> Js.Promise.then_(result => {
    Js.log(result)

    let _ = Js.Global.setTimeout(() => forever(), 1)

    Js.Promise.resolve()
  })
  |> ignore

let _ = forever()
2 Likes

What bothers me with the solutions that involve setTimeout or nextTick is that they break error handling. You can’t do something like this:

try {
  while (true) {
     await an_async_event()
     // ...
  }
} catch (err) {
  // ... all errors go here
}

Another reason why ReScript needs a support of async/await.

2 Likes