How do I properly Type an FFI that uses setTimeout?

#1

What background knowledge do I need in order to use a foreign function that uses setTimeout and a callback? I’m not sure where to start, or what about my current thinking is incorrect.

In short, I’m just trying to understand how to type asynchronous functions. But, I’m a bit stuck in not knowing what I don’t know in order to make it work correctly.

For instance, a non-async version I can manage.

javascript:

exports.myFunction = function(onSuccess) {
  return onSuccess("Hello!");
}

Purescript:

foreign import myFunction :: (String -> String) -> String 

and it works as expected. I can call it from the purescript side with no issue.

main = do 
    log (myFunction identity)

// Hello!

However, where I get stuck is in trying to make this same function delayed.

Javascript:

exports.myAsyncFunction(onSuccess) {
    setTimeout(function() {
        return onSuccess("Hello!");
    }, 1000);
}

I believe the “correct” way to approach this was via Effect, Like, we just wrap it up and all would be well.

Purescript:

foreign import myAsyncFunction :: (String -> String) -> Effect String 

However, the first problem I ran into was that it “over” invokes the function – I wasn’t ‘currying’ it enough. Adding in another function invocation.

exports.myAsyncFunction = function(onSuccess) {
  return function() {
    setTimeout(function() {
      return onSuccess("Hello!");
    }, 1000)
  }
}

And things no longer explode. However, my onSuccess method is never called.

main = do
  result <- myAsyncFunction (\s -> toUpper s)
  log result

Only undefined is shown in the output.

So, in short, I’ve got some core misunderstanding of how this should all work.

Can anyone point me to some resources which maybe go into the background knowledge of how to type asynchronous things?

#2

setTimeout returns a timer ID, not the result of the callback. The result of the callback won’t be available synchronously. If you want to stay in Effect, you’ll need to use a callback on the PureScript side too. See for example the binding in purescript-js-timers which uses the type

setTimeout :: Int -> Effect Unit -> Effect TimeoutId

The second argument is a callback which is invoked after the specified amount of time has passed.

1 Like