Sebastien Lorber Sebastien Lorber - 1 year ago 98
Scala Question

Throttle or debounce method calls

Let's say I have a method that permits to update some date in DB:

def updateLastConsultationDate(userId: String): Unit = ???

How can I throttle/debounce that method easily so that it won't be run more than once an hour per user.

I'd like the simplest possible solution, not based on any event-bus, actor lib or persistence layer. I'd like an in-memory solution (and I am aware of the risks).

I've seen solutions for throttling in Scala, based on Akka Throttler, but this really looks to me overkill to start using actors just for throttling method calls. Isn't there a very simple way to do that?

Edit: as it seems not clear enough, here's a visual representation of what I want, implemented in JS. As you can see, throttling may not only be about filtering subsequent calls, but also postponing calls (also called
trailing events
in js/lodash/underscore). The solution I'm looking for can't be based on pure-synchronous code only.

Answer Source

This sounds like a great job for a ReactiveX-based solution. On Scala, Monix is my favorite one. Here's the Ammonite REPL session illustrating it:

import $ivy.`io.monix::monix:2.1.0` // I'm using Ammonite's magic imports, it's equivalent to adding "io.monix" %% "monix" % "2.1.0" into your libraryImports in SBT

import scala.concurrent.duration.DurationInt
import monix.reactive.subjects.ConcurrentSubject
import monix.reactive.Consumer
import monix.eval.Task

class DbUpdater {
  val publish = ConcurrentSubject.publish[String]
  val throttled = publish.throttleFirst(1 hour)
  val cancelHandle = throttled.consumeWith(
    Consumer.foreach(userId =>
      println(s"update your database with $userId here")))

  def updateLastConsultationDate(userId: String): Unit = {

  def stop(): Unit = cancelHandle.cancel()

Yes, and with Scala.js this code will work in the browser, too, if it's important for you.