Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

redux saga, throttle/debounce conditionally?

Tags:

redux-saga

I'm logging banner impressions when it is visible on screen.

When a user scrolls, the same banner can be made visible multiple times in short time period.

And I'd like to prevent that.

On first thought, throttle is perfect way of preventing it.

But then when you have multiple banners in one page, throttle would not log the 2nd banner in screen if throttled.

So how can I throttle per key? (banner id as a key in this example) ie, I want to throttle banner impression per banner_id. (it's like servers throttling api_endpoint access per api keys)

EDIT

One could think of creating throttle for each key, but wonder if it could take up too much resources?

I wonder how API libraries such as Django-rest-framework implements throttle per api key. I guess it could be totally different from what saga throttle does.

like image 606
eugene Avatar asked Oct 17 '22 12:10

eugene


1 Answers

Using an expirable map

Things like django-rest use expirable map for throttling. An expirable set is sufficient for the task, too.

I cannot recommend an exact npm module for expirable map/set, unfortunately.

The pseudocode:

function* throttlePerKey(pattern, selector, timeout, saga) {
  const set = new ExpirableSet({ expire: timeout })

  while(true) {
    const action = yield take(pattern)
    const id = selector(action)
    const throttled = set.has(id)
    if (throttled) {
      // Do nothing, action throttled
    } else {
      set.add(id)
      yield call(saga, action)
    }
  }
}

Using only redux-saga

We can emulate an expirable set with redux-saga, and get a purely redux-saga solution.

The code:

function* throttlePerKey(pattern, selector, timeout, saga) {
  const set = new Set()

  while(true) {
    const action = yield take(pattern)
    const id = selector(action)
    const throttled = set.has(id)
    if (throttled) {
      // Do nothing, action throttled
    } else {
      set.add(id)
      // Expire items after timeout
      yield fork(function* () {
        yield delay(timeout)
        set.delete(id)
      })
      yield call(saga, action)
    }
  }
}
like image 143
Andrey Moiseev Avatar answered Oct 21 '22 00:10

Andrey Moiseev