How to bind to a js property that is a promise (not method)

Hi, I’m trying to do some bindings for the new View Transitions dom api, but I’m getting a little stuck as it has properties that are promises, but the property is not a method.

For example, the finished property is a property that needs to be awaited, but it is not a method call.
e.g. in js, it’s called like this:

  try {
    await transition.finished;
  } finally {
    document.documentElement.classList.remove("back-transition");
  }

I’m not to sure how to make this work in rescript. My code below is what I have so far, but unfortunately my code is currently calling the properties as if they were methods.


@module("react-dom") external reactFlushSync: (unit => unit) => unit = "flushSync"

type viewTransition

@send external skipTransition: (viewTransition, unit) => unit = "skipTransition"
@send external viewTransitionFinished: viewTransition => promise<unit> = "finished"
@send external viewTransitionReady: viewTransition => promise<unit> = "ready"
@send
external viewTransitionUpdateCallbackDone: viewTransition => promise<unit> = "updateCallbackDone"

@scope("document") @val
external maybeStartViewTransition: Nullable.t<(unit => unit) => viewTransition> =
  "startViewTransition"

let doViewTransition = callback => {
  switch Nullable.toOption(maybeStartViewTransition) {
  | Some(startViewTransition) =>
    // we will be using the methods that startViewTransition returns
    startViewTransition(() => {
      // This seems to be required to get view transition working with react: https://developer.chrome.com/docs/web-platform/view-transitions/#working-with-frameworks
      reactFlushSync(() => {
        callback()
      })
    })
  | None =>
    callback()
    // return an object with no-op methods for browsers that don't support the view transitions api
    %raw(`{
      skipTransition(){},
      get viewTransitionFinished(){ return Promise.resolve() },
      get viewTransitionReady(){ return Promise.resolve() },
      get viewTransitionUpdateCallbackDone(){ return Promise.resolve() },
    }`)
  }
}

let vt = doViewTransition(() => {
  // do something here
  ()
})

vt->skipTransition()

let foo = async () => {
  let finished = await vt->viewTransitionFinished
  Console.log(finished)
}

foo()->ignore

At the moment the output is:

async function foo() {
  var finished = await vt.finished();
  console.log(finished);
}

foo();

If i remove the @send it is converted to:

async function foo() {
  var finished$1 = await finished(vt);
  console.log(finished$1);
}

foo();

Which is still not right.

Oh wait, I think I figured it out. I just moved the properties into the type.

type viewTransition = {
  viewTransitionFinished: promise<unit>,
  viewTransitionReady: promise<unit>,
  viewTransitionUpdateCallbackDone: promise<unit>,
}

@send external skipTransition: (viewTransition, unit) => unit = "skipTransition"

Seems to be working now.

Hi Agent Coop! Glad to see you’re interested in ReScript :slight_smile:

There are multiple ways to bind to JS types and modules.
The “traditional” way is to make the JS type an abstract type and use @send to bind to methods of this type and @get to bind to its properties (that’s the decorator you were missing in this case):

type viewTransition 

@get external viewTransitionFinished:  viewTransition => promise<unit> = "viewTransitionFinished"
@get external viewTransitionReady: viewTransition => promise<unit> = "viewTransitionReady"
@get external viewTransitionUpdateCallbackDone: viewTransition => promise<unit> = "viewTransitionUpdateCallbackDone"
@send external skipTransition: (viewTransition, unit) => unit = "skipTransition"

The other way is to make use of the uncurried mode and directly write methods and properties as fields of a record, as you’ve done:

type viewTransition = {
  viewTransitionFinished: promise<unit>,
  viewTransitionReady: promise<unit>,
  viewTransitionUpdateCallbackDone: promise<unit>,
  skipTransition: unit => unit,
}

This, though, has the drawback of only allowing one way to bind to a given property or method. It might be more suitable for simple types that have mostly imperative methods (methods that return unit).
It might be the case here.

1 Like