redux-saga / redux-saga

An alternative side effect model for Redux apps

Home Page:https://redux-saga.js.org/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

could we add leading/trailing edge options for debounce?

ensconced opened this issue · comments

This is more of a feature request than a bug. If you think this feature might make sense then maybe I will raise a PR for it.

Could we add options for debounce to configure whether the saga is called on the leading edge and/or the trailing edge? (Compare to the lodash debounce function).

This would be a very useful feature because by setting leading: true I can ensure I respond immediately to user actions without having to wait for the debounce period to expire.

Perhaps the first delayLength parameter could either be number, or an options object like { delayLength: number; leading?: boolean; trailing?: boolean }?

Hi! Thanks so much for reaching out.

First and foremost: redux-saga is in a stable state and as such backwards compatibility is our primary directive. If you can implement the ability to change debounce -- and throttle -- in a non-breaking way, I'd be happy to review and approve.

I'm also happy to answer any questions you have.

I made a start on this. It seems straightforward enough for debounce - my initial attempt at that is here (work in progress) but throttle is a little trickier.

I think the current implementation of throttle would correspond to the leading: true, trailing: true case. For the leading, true, trailing: false case, I think it's as simple as just using buffers.none() instead of buffers.sliding(1)). And leading: false, trailing: false should be simple too (since it basically does nothing).

However, I'm not sure how to approach the leading: false, trailing: true case for throttle.
I wanted to use an fsm like this:

fsmIterator(
  {
    q1() {
      const yActionChannel = { done: false, value: actionChannel(patternOrChannel, buffers.sliding(1)) }
      return { nextState: 'q2', effect: yActionChannel, stateUpdater: setChannel }
    },
    q2() {
      return { nextState: 'q3', effect: yTake(), stateUpdater: setAction }
    },
    q3() {
      return { nextState: 'q4', effect: yDelay }
    },
    q4() {
      // If another action has been received since the leading edge,
      // use that for the trailing edge.
      return channel.isEmpty()
        ? { nextState: 'q5', effect: yTake, stateUpdater: setAction }
        : { nextState: 'q5', effect: yNoop }
    },
    q5() {
      return { nextState: 'q2', effect: yFork(action) }
    },
  },
  needsChannel ? 'q1' : 'q2',
  `throttle(${safeName(patternOrChannel)}, ${worker.name})`,
)

...but channel.isEmpty() is a non-existent method. I'm not sure how to achieve this without some way of checking whether the channel is empty.

Hi! You are welcome to just focus on debounce for now if that makes things simpler for you.