[ANN] Async/await is coming to ReScript!

My promises are just the platform promises. The only abstraction introduced is in the catch function error handling to allow proper error matching. It’s as low as it can get binding wise.

Tasks are an extra abstraction around async operations with more directed control flow and (depending on the library) more possibilities to process async results. Task controlled async operations may be type-safer and less prone to runtime errors, but you need to wrap things and do some extra work, especially in a binding situation.

Considering the complexities of JS apps (and libraries), and the weird usage of promises in code I don’t control, I concluded that keeping it bare bone and just keep on using promises is easier for me.

2 Likes

The following code:

let foo = () => {
  let y = {
    let x = Js.Math.random_int(0, 100);
    x + 1
  }
  y + 2
}

compiles to:

function foo(param) {
  var x = Js_math.random_int(0, 100);
  var y = x + 1 | 0;
  return y + 2 | 0;
}

I would expect

let foo = async () => {
  let y = {
    let x = await asyncInt()
    x + 1
  }
  y + 2
}

to produce the following:

function async foo(param) {
  var x = await asyncInt();
  var y = x + 1 | 0;
  return y + 2 | 0;
}

I’m not sure if that’s what the ReScript’s implementation of async/await does, but that’s what I would expect.

3 Likes

I see async/await in JS more-or-less a tool to streamline the flow of Promises.
In such perspective, we verify the places await can prepended to–an expression of a Promise type.
Thus in the second example, p is just an int and cannot be awaited.

1 Like

The current 10.1.0-alpha.2 npm release (as well as master) introduces a warning whenever nested promises are used, as this is known to introduce unsoundness. See discussion at the top of this thread.

There are alternative promise implementations that get around the problem by giving a special representation for nested promises.

In this exploration https://github.com/rescript-lang/rescript-compiler/issues/5707 a different approach is taken where it seems that one can safely type promises without using any special runtime representation. This is work in progress, the investigation is only semi-formal, but seems to work just fine on the problematic examples.

9 Likes

Hello,

how to handle multiple promises? Should I use pattern matching like this:

let test1 = async () => {
  Js.log("start")

  switch (await wait("hello"), await wait("world")) {
  | (_, _) => Js.log("finished")
  | exception JsError(_) => Js.log("error")
  }
}

What’s the point of using a switch statement at all? The following code looks cleaner, produces slightly less verbose js code and works the same:

let test2 = async () => {
  try {
    Js.log("start")

    await wait("hello")
    await wait("world")

    Js.log("finished")
  } catch {
  | Js.Exn.Error(_) => Js.log("error")
  }
}

Cheers,
Daniel

1 Like

The plugin does not handle pipe well

let getData = async (url: string) => {
  let respones = await Fetch.fetch(url) // I have error here
  ->then(Fetch.Response.arrayBuffer)

  // do any thing with respones
}

image

let respones = await fetch("").then(respones => respones)
1 Like

You should use either await or then. Not both at the same time

1 Like

This should work. We’d have to see where that then function is actually coming from, but the problem right now looks like it is that then is designed to continue a promise chain, so then is expecting you to return a new promise. You probably want thenResolve (if you’re using the new promise bindings).

This example code compiles and does what you’d expect:

type person = {name: string}

@val
external fetchData: unit => promise<person> = "fetch"

let fn = async () => {
  let personName = await fetchData()->Promise.thenResolve(person => person.name)
  personName
}
1 Like

I am returning a promise already

As I said before, you are combining two more or less contrary concepts.

await something() doesn’t return a promise - it returns the unwrapped value (person in your case).

If you don’t use the await keyword, you will get a promise. So you can call Promise.then(something(), ...) or something()->Promise.then(...).

It is the same like in JavaScript

async function foo() {
  await 1;
}

vs.

function foo() {
  return Promise.resolve(1).then(() => undefined);
}

So in your example, the code could look like this:

let fn = async () => {
  let person = await fetchData()

  person.name
}

Maybe it’s because of the unexpected operator precedence (await executed earlier than ->)

3 Likes