Taking async/await to the next level

I’m running the alpha build with async/await. As I posted in some other thread, I don’t really find async/await as useful in ReScript as in JS. I’m still unsure why. Usually, I end up doing a series of promise then’s with flatmap to carry to the error quickly to the end or do some work.

That gives quite a bit of nested scopes. Sometimes it’s a problem, sometimes not. What I’m thinking is, couldn’t we combine the two to have the neatness of async/await and still the utility of Results?

I’m imagining something like this:

let db = flat Db.getConn()
let user = flat db->Db.User.findByEmail("some@email.com")
let config = flat db->Db.Config.findForUser(user)

switch config {
| Ok(cfg) => // ...
| Error(#COULD_NOT_CONNECT_TO_DB) => // would from let db = ... line
| Error(#NO_SUCH_USER) => // would from let user = ... line
| Error(_) => // ...
}

Where each op returns a Result and the flat keyword (whatever keyword is chosen) is basically an async Result.flatMap op. Is this a good idea? Is it a bad idea? What do you think?

Hi, I am new here and haven’t used async/await so far. But I was wondering if it is possible to chain these promises together

let config = await Db.getConn()
->then(async db => await Db.User.findByEmail("some@email.com"))
->then(async user => await db->Db.Config.findForUser(user))

switch config {
| Ok(cfg) => // ...
| Error(#COULD_NOT_CONNECT_TO_DB) => // would from let db = ... line
| Error(#NO_SUCH_USER) => // would from let user = ... line
| Error(_) => // ...
}

Hey there,

Although it could be rewritten similarly to your code (and also because the example code I gave was trivial), you do not have the db binding in the second closure in your code

->then(async user => await db->Db.Config.findForUser(user))

There is no db there, so it won’t compile. So you need to introduce nesting, which is what I’m trying to avoid. :slight_smile:

Here’s an alternative way how to write your code. It uses exception, but since it’s not thrown outside, the code is pretty-much safe.

let main = {
  exception Internal('a)
  () => {
    try {
      let db = (await Db.getConn())->ResultX.fold(error => {
        switch error {
          | #OUT_OF_MEMORY => raise(Internal(#COULD_NOT_CONNECT_TO_DB))
        }
      })
      let user = (await db->Db.User.findByEmail("some@email.com"))->OptionX.foldOrRaise(Internal(#NO_SUCH_USER))
      let config = (await db->Db.Config.findForUser(user))->OptionX.foldOrRaise(Internal(#USER_WITHOUT_CONFIG))
      config
    } catch {
      | Internal(#COULD_NOT_CONNECT_TO_DB) => // would from let db = ... line
      | Internal(#NO_SUCH_USER) => // would from let user = ... line
      | Internal(_) => // ...
    }
  }
}

Sorry, I didn’t notice that.

let config = await Db.getConn()
->Belt.Result.flatMap(async db => await Db.User.findByEmail("some@email.com")->Belt.Result.Map(user => (db, user)))
->Belt.Result.flatMap(async (db, user) => await db->Db.Config.findForUser(user)))

switch config {
| Ok(cfg) => // ...
| Error(#COULD_NOT_CONNECT_TO_DB) => // would from let db = ... line
| Error(#NO_SUCH_USER) => // would from let user = ... line
| Error(_) => // ...
}

Probably it works