Search code examples
scala.jstimedelay

Implement typing delay in ScalaJS?


I have a search input field in a ScalaJS app that fires off requests to a backend server whilst the user types in a city name. However, I need to implement a delay so that the request is not fired until after a certain delay (say 1000ms). Without such a delay, there is the chance that I'll get back false positives on the search (E.G. If the user wants to search for "paris", then there will be a false hit on "par" - a small town in Cornwall, England - when the third character is entered)

I've tried transcribing the JavaScript equivalent into Scala, but the setTimeout part doesn't seem to work.

import scala.scalajs.js.timers.{SetTimeoutHandle, clearTimeout, setTimeout}

private def delay = () => {
  // Set initial timeout to do nothing after 0 ms
  var handle: SetTimeoutHandle = setTimeout(0)(() => {})

  (fn: Function0[Unit], ms: Double) => {
    clearTimeout(handle)
    handle = setTimeout(ms)(fn)
  }
}

Then I'm handling the user input event using an Akka Actor

def receive = {
  /************************************************
   * Client event
   * The user has typed something into the search field
   */
  case evt: Event =>
    delay()(handleInput, 1000.0)
}

Where handleInput is the zero parameter function that obtains the user's input and then fires off a request to the backend.

The anonymous inner function that clears and then resets the timeout is executed, but the handleInput function never gets called

Thanks

Chris W


Solution

  • The problem in your code is that you are giving a function of type () => Unit to setTimeout, but setTimeout takes a by-name parameter. In other words, the argument to setTimeout should be a statement to execute when the timeout expires. If you give it a function, then after the timeout that function value will be evaluated, but the function will not be called!

    It is similar to mistakenly trying to do

    val result = fn // result is the *function* itself, but does not call it
    

    instead of

    val result = fn() // fn is called, result is what it returns
    

    You can fix your call to setTimeout by replacing fn by fn(). Also, it is typically more idiomatic, in those circumstances, to use {} instead of () for the parameter to setTimeout, which also gives a visual clue that it is a by-name parameter:

    (fn: Function0[Unit], ms: Double) => {
      clearTimeout(handle)
      handle = setTimeout(ms) {
        fn()
      }
    }
    

    You should also adapt your first dummy setTimeout for consistency, although since it is a dummy anyway, that will not change the behavior:

    // Set initial timeout to do nothing after 0 ms
    var handle: SetTimeoutHandle = setTimeout(0) {}