Goga Koreli
Goga Koreli

Reputation: 2947

How does throttleTime operator's config parameter work? (ThrottleConfig)

I have read the throttleTime documentation, but I don't get the operator fully.

I know how throttleTime(1000) works. After an event arrives it will skip all subsequent events for 1 second and then start this process again.

What I have trouble to understand is how exactly ThrottleConfig works, which is the third parameter of the operator.

throttleTime<T>(
  duration: number, 
  scheduler: SchedulerLike = async, 
  config: ThrottleConfig = defaultThrottleConfig): MonoTypeOperatorFunction<T>

How do leading and trailing properties change the functionality of the source Observable?

I have read many documentations but they don't clearly explain this.

So there are four options:

  1. { leading: true, trailing: false }:
    default option, after receiving event skips other events for specified duration and then repeat.
  2. { leading: false, trailing: true }:
    ???
  3. { leading: false, trailing: false }:
    Tested this and the Observable doesn't emit anything at all.
  4. { leading: true, trailing: true }:
    ???

Upvotes: 19

Views: 5161

Answers (3)

frido
frido

Reputation: 14109

throttleTime will start a new throttle interval (a time period in which no items will be emitted) when it receives a new value and isn't already throttled. The length of this throttle interval is determined by the duration you supply.

With RxJS 7 a new throttle interval is also started when a trailing value is emitted at the end of a throttle interval.

leading and trailing specify whether an item should be emitted at the beginning or end of a throttle interval.

leading: Emit an item at the beginning of a new throttle interval.

trailing: Emit the last item received from the source at the end of a throttle interval.

Visualisation

RxJS 6 & 7 - trailing: false

throttleTime(12 ticks, { leading: true, trailing: false })

source_1:           --0--1-----2--3----4--5-6---7------------8-------9---------
throttle interval:  --[~~~~~~~~~~~]----[~~~~~~~~~~~]---------[~~~~~~~~~~~]-----
output_1:           --0----------------4---------------------8-----------------


source_2:           --0--------1------------------2--------------3---4---------
throttle interval:  --[~~~~~~~~~~~]---------------[~~~~~~~~~~~]--[~~~~~~~~~~~]-
output_2:           --0---------------------------2--------------3-------------
throttleTime(12 ticks, { leading: false, trailing: false })

source_1:           --0--1-----2--3----4--5-6---7------------8-------9---------
throttle interval:  --[~~~~~~~~~~~]----[~~~~~~~~~~~]---------[~~~~~~~~~~~]-----
output_1:           -----------------------------------------------------------


source_2:           --0--------1------------------2--------------3---4---------
throttle interval:  --[~~~~~~~~~~~]---------------[~~~~~~~~~~~]--[~~~~~~~~~~~]-
output_2:           -----------------------------------------------------------

RxJS 6 - trailing: true

throttleTime(12 ticks, { leading: true, trailing: true })

source_1:           --0--1-----2--3----4--5-6---7------------8-------9---------
throttle interval:  --[~~~~~~~~~~~]----[~~~~~~~~~~~]---------[~~~~~~~~~~~]-----
output_1:           --0-----------3----4-----------7---------8-----------9-----


source_2:           --0--------1------------------2--------------3---4---------
throttle interval:  --[~~~~~~~~~~~]---------------[~~~~~~~~~~~]--[~~~~~~~~~~~]-
output_2:           --0-----------1---------------2--------------3-----------4-
throttleTime(12 ticks, { leading: false, trailing: true })

source_1:           --0--1-----2--3----4--5-6---7------------8-------9---------
throttle interval:  --[~~~~~~~~~~~]----[~~~~~~~~~~~]---------[~~~~~~~~~~~]-----
output_1:           --------------3----------------7---------------------9-----


source_2:           --0--------1------------------2--------------3---4---------
throttle interval:  --[~~~~~~~~~~~]---------------[~~~~~~~~~~~]--[~~~~~~~~~~~]-
output_2:           --------------1---------------------------2--------------4-

RxJS 7 - trailing: true

throttleTime(12 ticks, { leading: true, trailing: true })

source_1:           --0--1-----2--3----4--5-6---7------------8-------9---------
throttle interval:  --[~~~~~~~~~~~I~~~~~~~~~~~I~~~~~~~~~~~I~~~~~~~~~~~I~~~~~~~~
output:             --0-----------3-----------6-----------7-----------9--------


source_2:           --0--------1------------------2--------------3---4---------
throttle interval:  --[~~~~~~~~~~~I~~~~~~~~~~~]---[~~~~~~~~~~~]--[~~~~~~~~~~~I~
output_2:           --0-----------1---------------2--------------3-----------4-
throttleTime(12 ticks, { leading: false, trailing: true })

source_1:           --0--1-----2--3----4--5-6---7------------8-------9---------
throttle interval:  --[~~~~~~~~~~~I~~~~~~~~~~~I~~~~~~~~~~~I~~~~~~~~~~~I~~~~~~~~
output:             --------------3-----------6-----------7-----------9--------


source_2:           --0--------1------------------2--------------3---4---------
throttle interval:  --[~~~~~~~~~~~I~~~~~~~~~~~]---[~~~~~~~~~~~I~~~~~~~~~~~]----
output_2:           --------------1---------------------------2-----------4----

Upvotes: 54

S&#225;mal Rasmussen
S&#225;mal Rasmussen

Reputation: 3505

The expected output for { leading: true, trailing: true } has changed in rxjs 7: https://github.com/ReactiveX/rxjs/commit/ea84fc4dce84e32598701f79d9449be00a05352c

It will now ensure the spacing between throttles is always at least the throttled amount.

So the throttle interval will repeat immediately if there was a trailing emit. This makes the example from @frido change to something like this:

source:              --0--1-----2--3----4--5-6---7------------8-------9--------
throttle interval:   --[~~~~~~~~~~~x~~~~~~~~~~~x~~~~~~~~~~~x~~~~~~~~~~~x~~~~~~~
output:              --0-----------3-----------6-----------7-----------9-------

I have recreated the example from @frido with a valid test case below.

const testScheduler = new rxjs.testing.TestScheduler((actual, expected) => {
  const actualString = JSON.stringify(actual);
  const expectedString = JSON.stringify(expected)
  console.log(actualString)
  console.log(expectedString);
  console.log(expectedString === actualString);
});
testScheduler.run((helpers) => {
  const { cold, time, expectObservable, expectSubscriptions } = helpers;
  const e1 = cold(' --0--1-----2--3----4--5-6---7------------8-------9--------|');
  const expected = '--0-----------3-----------6-----------7-----------9-------|';
  const e1subs = '  ^---------------------------------------------------------!';
  const t = time('  ------------|'); // t = 12

  expectObservable(e1.pipe(
      rxjs.operators.throttleTime(t, null, { leading: true, trailing: true })
  )).toBe(expected);
  expectSubscriptions(e1.subscriptions).toBe(e1subs);
});
<script src="https://unpkg.com/[email protected]/dist/bundles/rxjs.umd.js"></script>

Upvotes: 4

Shameet
Shameet

Reputation: 113

This question probably needs more detailed explanation for other scenarios when the Source observable is Not a sequence numbers in a fixed time interval.

The explanation on the official site is for the scenario is for { leading: true, trailing: false }, which is the default behavior as well.

Based on the official explanation here

There is an internal timer with two states Enabled and Disabled.

When the timer is Enabled NO values can pass through.

When the timer is Disabled, then, as soon as the First source value arrives (after the timer disabled event) that value is forwarded to output observable, and then the Timer is enabled. It remains enabled for the time span specified in duration parameter.

The important fact to note here is that the Time span of Timer being Disabled is not fixed and depends on the source observable value arrival. (As can be seen in image below)

I looked at thinkrx.io site for throttle time behavior and was able to reproduce the results but still took some time to trace the exact behavior. Attaching the marble image from ThinkRx site with markings.

Please note that in ThinkRx marble diagrams, the Color represents the observable values and the numbers are time instance.

enter image description here

In the image, In case of (Leading True)

the Red horizontal marking is when Timer is enabled (no values can pass) the Green horizontal marking is when Timer is disabled. Pls note that the Timer is disabled more time or less time than 100 ms.

Initially Timer is disabled and it allows 0 to pass through and becomes enabled. After 100ms it becomes disabled and remains in that state until 230, passes 230 and becomes enabled again and so on.

For the configuration setting of Trailing True, the Timer spans are exactly same based on the Leading True login, although instead of passing the First value, it waits till the end of 100ms and then passes on the last value until that point.

For both true, it passes the both values leading and trailing

Both false does not pass anything.

Upvotes: 0

Related Questions