Is there an operator in RxJS that debounces without delaying the "first event in a burst", but delaying (and always emitting) the "last event in a burst"?
Something like this:
---a----b-c-d-----e-f---
after awesome-debounce(2 dashes) becomes:
---a----b------d--e----f
while a normal debounce would be:
-----a---------d-------f
It's kind of a mix between throttle and debounce...
Hmmm, this is the easiest solution I can think of. The interesting part for you is the awesomeDebounce() function that creates the sub-chain.
It basically just combines throttle() and debounceTime() operators:
const Rx = require('rxjs');
const chai = require('chai');
let scheduler = new Rx.TestScheduler((actual, expected) => {
chai.assert.deepEqual(actual, expected);
console.log(actual);
});
function awesomeDebounce(source, timeWindow = 1000, scheduler = Rx.Scheduler.async) {
let shared = source.share();
let notification = shared
.switchMap(val => Rx.Observable.of(val).delay(timeWindow, scheduler))
.publish();
notification.connect();
return shared
.throttle(() => notification)
.merge(shared.debounceTime(timeWindow, scheduler))
.distinctUntilChanged();
}
let sourceMarbles = '---a----b-c-d-----e-f---';
let expectedMarbles = '---a----b------d--e----f';
// Create the test Observable
let observable = scheduler
.createHotObservable(sourceMarbles)
.let(source => awesomeDebounce(source, 30, scheduler));
scheduler.expectObservable(observable).toBe(expectedMarbles);
scheduler.flush();
The inner notification Observable is used only for the throttle() operator so I can reset its timer manually when I need. I also had to turn this Observable into "hot" to be independent on the internal subscriptions from throttle().
That's indeed useful debounce type for many situations. Use merge, throttleTime and debounceTime in a next way:
Rx.Observable.merge(source.throttleTime(1000), source.debounceTime(2000))
Full example is here http://jsbin.com/godocuqiwo/edit?js,console
Note: it will emit not only first and last value in debounce interval but also values produced by throttle (which is usually expected and needed, as for scroll debouncing for example).
Related
I have an observable that performs some relatively taxing work so I need to use the shareReplay(1) operator, it's important that I have access to an emitted value immediately as well so share won't quite achieve everything I need.
The issue is that when I'm trying to cleanup and unsubscribe the source observable will keep emitting, which I believe is caused by shareReplay keeping it alive due to refCount defaulting to false. This is also the behavior I need as at application start up the reference count will jump from 1 to 0 to 1 essentially, I don't want to observable restarting on a new subscription.
Is there any way to unsubscribe and stop the source observable emitting when using shareReplay? Seems a bit strange to have no way to clean up resources when using that particular operator.
A simple example:
const first = interval(10_000).pipe(
startWith(0),
withLatestFrom(someOtherObs),
map(([i, val]) => {
// some work
}),
shareReplay(1)
)
const second = interval(5_000).pipe(
withLatestFrom(first),
take(5)
)
const main = interval(5_000).pipe(
withLatestFrom(first),
map(([i, val]) => {
// perform work
})
)
const app = from([second, main]).pipe(concatAll()).subscribe()
Basically second will complete before main will start (hence the need for shareReplay), however it will continue emitting in perpetuity due to the interval... Is there anything I can do to avoid this while achieving the same behavior... Any help would be greatly appreciated.
In your case you'll need to force the source Observable of shareReplay(1) to complete for example with takeUntil() operator.
const done$ = new Subject();
const first = interval(10_000).pipe(
startWith(0),
withLatestFrom(someOtherObs),
map(([i, val]) => {
// some work
}),
takeUntil(done$),
shareReplay(1)
)
// ...
done$.next();
I need a specific behavior that I can't get with the RxJS operators. The closest would be to use DebounceTime only for values entered after the first one, but I can't find a way to do it. I have also tried with ThrottleTime but it is not exactly what I am looking for, since it launches intermediate calls, and I only want one at the beginning that is instantaneous, and another at the end, nothing else.
ThrottleTime
throttleTime(12 ticks, { leading: true, trailing: true })
source: --0--1-----2--3----4--5-6---7------------8-------9---------
throttle interval: --[~~~~~~~~~~~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-
DebounceTime
debounceTime(500)
source: --0--1--------3------------4-5-6-7-8-9-10-11--13----------------
debounce_interval: -----[~~~~~]--[~~~~~]--------------------------[~~~~~]----------
output: -----------1--------3--------------------------------13---------
What I want
debounceTimeAfterFirst(500) (?)
source: --0--1--------3------------4-5-6-7-8-9-10-11--13----------------
debounce_interval: -----[~~~~~]--[~~~~~]--------------------------[~~~~~]----------
output: --0--------1--3------------4-------------------------13---------
As you see, the debounce time is activated when a new value is entered. If the debounce time passes and any new value has been entered, it stops the listening the debounceTime action and waits to start a new one.
Edit: I forgot to comment that this must be integrated with NgRx’s Effects, so it must be a continuous stream that mustn't be completed. Terminating it would probably cause it to stop listening for dispatched actions.
I would use a throttle combined with a debounceTime:
throttle: from Documentation Emit value on the leading edge of an interval, but suppress new values until durationSelector has completed.
debounceTime: from Documentation Discard emitted values that take less than the specified time between output.
I would use a throttle stream to get the raising edge (the first emission) and then the debounce stream would give us the falling edge.
const source = fromEvent(document.getElementsByTagName('input'), 'keyup').pipe(
pluck('target', 'value')
);
const debounced = source.pipe(
debounceTime(4000),
map((v) => `[d] ${v}`)
);
const effect = merge(
source.pipe(
throttle((val) => debounced),
map((v) => `[t] ${v}`)
),
debounced
);
effect.subscribe(console.log);
See RxJS StackBlitz with the console open to see the values changing.
I prepared the setup to adapt it to NgRx which you mention. The effect I got working is:
#Injectable({ providedIn: 'root' })
export class FooEffects {
switchLight$ = createEffect(() => {
const source = this.actions$.pipe(
ofType('[App] Switch Light'),
pluck('onOrOff'),
share()
);
const debounced = source.pipe(debounceTime(1000), share());
return merge(source.pipe(throttle((val) => debounced)), debounced).pipe(
map((onOrOff) => SetLightStatus({ onOrOff }))
);
});
constructor(private actions$: Actions) {}
}
See NgRx StackBlitz with the proposed solution working in the context of an Angular NgRx application.
share: This operator prevents the downstream paths to simultaneously fetch the data from all the way up the chain, instead they grab it from the point where you place share.
I also tried to adapt #martin's connect() approach. But I don't know how #martin would "reset" the system so that after a long time if a new source value is emitted would not debounce it just in the same manner as you first run it, #martin, feel free to fork it and tweak it to make it work, I'm curious about your approach, which is very smart. I didn't know about connect().
#avicarpio give it a go on your application and let us know how it goes :)
I think you could do it like the following, even though I can't think of any easier solution right now (I'm assuming you're using RxJS 7+ with connect() operator):
connect(shared$ => shared$.pipe(
exhaustMap(value => merge(
of(value),
shared$.pipe(debounceTime(1000)),
).pipe(
take(2),
)),
)),
Live demo: https://stackblitz.com/edit/rxjs-qwoesj?devtoolsheight=60&file=index.ts
connect() will share the source Observable and lets you reuse it in its project function multiple times. I'm using it only to use the source Observable inside another chain.
exhaustMap() will ignore all next notifications until its inner Observable completes. In this case the inner Observable will immediately reemit the current value (of(value)) and then use debounceTime(). Any subsequent emission from source is ignored by exhaustMap() because the inner Observable hasn't completed yet but is also passed to debounceTime(). Then take(2) is used to complete the chain after debounceTime() emits and the whole process can repeat when source emits because exhaustMap() won't ignore the next notification (its inner Observable has completed).
Here's a custom operator that (as far s I can tell) does what you're after.
The two key insights here are:
Use connect so that you can subscribe to the source twice, once to ignore emissions with exhaustMap and another to inspect and debounce emissions with switchMap
Create an internal token so that you know when to exit without a debounced emission. (Insures that from your example above, the 4 is still emitted).
function throttleDebounceTime<T>(interval: number): MonoTypeOperatorFunction<T> {
// Use this token's memory address as a nominal token
const resetToken = {};
return connect(s$ => s$.pipe(
exhaustMap(a => s$.pipe(
startWith(resetToken),
switchMap(b => timer(interval).pipe(mapTo(b))),
take(1),
filter<T>(c => c !== resetToken),
startWith(a)
))
));
}
example:
of(1,2,3,4).pipe(
throttleDebounceTime(500)
).subscribe(console.log);
// 1 [...0.5s wait] 4
I am trying to understand why share RxJs operator works differently if the source Observable is created with range instead of timer.
Changing the original code to:
const source = range(1, 1)
.pipe(
share()
)
const example = source.pipe(
tap(() => console.log('***SIDE EFFECT***')),
mapTo('***RESULT***'),
)
const sharedExample = example
const subscribeThree = sharedExample.subscribe(val => console.log(val))
const subscribeFour = sharedExample.subscribe(val => console.log(val))
Results in:
console.log src/pipeline/foo.spec.ts:223
SIDE EFFECT
console.log src/pipeline/foo.spec.ts:228
RESULT
console.log src/pipeline/foo.spec.ts:223
SIDE EFFECT
console.log src/pipeline/foo.spec.ts:229
RESULT
Basically, the side effect is invoked more than once.
As far as I know range is supposed to be a cold observable but it is said that share should turn cold observables to hot.
What is the explanation behind this behaviour ?
Two things to point out.
First, if you look closely at the function signature for range, you'll see it takes a third parameter, a SchedulerLike.
If unspecified, RxJS calls the next handler of each subscriber immediately with the relevant value for the range observable until it's exhausted. This isn't desirable if you intend to use the share operator, because it effectively bypasses any shared side effect processing that might be introduced.
Relevant snippet taken from the actual implementation:
// src/internal/observable/range.ts#L53
do {
if (index++ >= count) {
subscriber.complete();
break;
}
subscriber.next(current++);
if (subscriber.closed) {
break;
}
} while (true);
timer also takes an optional SchedulerLike argument. If unspecified, the implementation adopts AsyncScheduler by default, different to the default for range.
Secondly, the share operator should follow all other operators that might have side effects. If it precedes them, the expected unifying behaviour of pipe operator processing is lost.
So, with both points in mind, to make the share operator work with range as you're expecting:
const { asyncScheduler, range, timer } = rxjs;
const { mapTo, tap, share } = rxjs.operators;
// Pass in an `AsyncScheduler` to prevent immediate `next` handler calls
const source = range(1, 1, asyncScheduler).pipe(
tap(() => console.log('***SIDE EFFECT***')),
mapTo('***RESULT***'),
// All preceding operators will be in shared processing
share(),
);
const sub3 = source.subscribe(console.log);
const sub4 = source.subscribe(console.log);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.4.0/rxjs.umd.min.js"></script>
With the pausable operator not implemented in RxJS v5, is there a better way to create a pausable interval? The code below works, but does so by keeping track of the last emitted value as an offset. It seems like there should be a better way...
const source = Rx.Observable.interval(100).share()
const offset = new Rx.BehaviorSubject(0)
let subscription;
let currentValue;
function start() {
subscription = source
.subscribe(i => {
currentValue = i + offset.value
})
}
function pause() {
source.take(1).subscribe(i => offset.next(i + offset.value))
subscription.unsubscribe()
}
The share() operator is an alias for .publish().refCount(). The refCount() means that the observable will clean itself up when no other subscribers to it exist. Because you're unsubscribing from source, it will clean itself up, then restart when subscribed to again. Use publish() with connect() instead. Here's the code:
const source = Observable.interval(100).publish();
source.connect();
// Start with false, change to true after 200ms, then false again
// after another 200ms
const pauser = Observable.timer(200)
.mapTo(true)
.concat(Observable.timer(200).mapTo(false))
.startWith(false);
const pausable = pauser
.switchMap(paused => (paused ? Observable.never() : source))
.take(10);
pausable.subscribe(x => console.log(x));
See this jsbin for a running example: http://jsbin.com/jomusiy/3/edit?js,console.
There's no general purpose way to do this. It depends on what exactly you mean by pausing and also what you're pausing. (Do you want to just stop emitting and then start again, do you buffer and drain, or do you effectively need to add a delay to the upstream, but otherwise preserve the distribution of upstream values in time?)
I have a way to do this in an efficient manner when the upstream is specifically a timer, as it is in your example. It is the answer to my own question here.
RxJS (5.0rc4): Pause and resume an interval timer
This one has the great advantage of preserving the source's value distribution in time, but just adding a delay to it.
For more general cases:
For cold observables: switch between a never and upstream. On pause, unsubscribe from upstream. skip ones that you've seen, and then switch to the skiped stream. While unpaused you must maintain a count of how many values you've emitted, so that you can skip that many the next time someone unpauses. You only have to remember how many you've seen before. However, every unpause causes the cold observable to replay from the beginning. This could be very inefficient in the general case. The code would look something like this. pauser here would be a Subject that you can set to true or false to pause upstream.
function pausableCold(pauser, upstream) {
var seen = 0;
return pauser.switch(paused => {
if (paused) {
return Observable.never();
}
else {
return upstream.skip(seen).do(() => seen++);
}
});
}
For hot or cold observables, you can use buffering. buffer while paused and then drain and concat to the hot upstream when unpaused. (This preserves all the values but but it doesn't preserve their distribution in time. Also, you should hot upstream with publish if it could be cold.)
The most efficient way is not really part of the Rx. What you really want is to tell the source to stop emitting and then start again. The way you do that is very specific to what the source is and how the source is generating values.
I wonder how to implement this properly with RxJs (4/5)?
-a-- -b----c----d-----------------------------------------------------------e------f---------------------
-5-sec after-"a"--> [abcd]---new 5 sec timer will start when "e" emited-----5 sec-after-"e"->[ef]-
I think this:
.buffer(source$.throttleTime(5000).debounceTime(5000))
do the job in rxjs 5
Your best shot is to use buffer. The buffer has a closing condition, and you'd like a closing condition 5 seconds after a new item was introduced. So, lets suppose you have a source stream, your desired stream will be:
source.buffer(source.throttle(5100).debounce(5000));
This is rxjs 4. I think rxjs has a slightly different buffer operators but the idea is the same.
Explanation:
The throttle ensures that for 5100 mSecs you will get only the first "tick". The debounce will propagate this "tick" after 5000 mSecs because there were no other "ticks" since. Note that I chose 5100 mSecs since the timing is not always perfect and if you use 5000 mSecs for both, the debounce might be repeatedly delayed and you'll get starvation. Anyways, your buffer will not loose data, just might group it in chunks bigger than 5000 mSecs.
Rxjs 5 has a bufferToggle operator which might look a better option, yet, the fact that you both open and close the buffer might become risky and make you loose data due to timing issues.
I am using RxJS 6 and could not readily find the documentation for 5. However, this is a fantastic question. Here was my result which is also demonstrated in a real example reproducing a bug in Angular Material.
source$ = source$.pipe(buffer(source$.pipe(debounceTime(5000))));
Having tried all Rxjs 5 buffer variants, in particular bufferTime which emits every n seconds empty or not, I ended up rolling my own bufferTimeLazy:
function bufferTimeLazy(timeout) {
return Rx.Observable.create(subscriber => {
let buffer = [], hdl;
return this.subscribe(res => {
buffer.push(res);
if (hdl) return;
hdl = setTimeout(() => {
subscriber.next(buffer);
buffer = [];
hdl = null;
}, timeout);
}, err => subscriber.error(err), () => subscriber.complete());
});
};
// add operator
Rx.Observable.prototype.bufferTimeLazy = bufferTimeLazy;
// example
const click$ = Rx.Observable.fromEvent(document, 'click');
click$.bufferTimeLazy(5000).subscribe(events => {
console.log(`received ${events.length} events`);
});
Example:
https://jsbin.com/nizidat/6/edit?js,console,output
The idea is to collect events in a buffer and emit the buffer n seconds after first event. Once emitted, empty buffer and remain dormant until next event arrives.
If you prefer not to add operator to Observable.prototype, just invoke the function:
bufferTimeLazy.bind(source$)(5000)
EDIT:
Ok, so it's not all bad with Rxjs 5:
var clicks = Rx.Observable.fromEvent(document, 'click').share();
var buffered = clicks.bufferWhen(() => clicks.delay(5000));
buffered.subscribe(x => console.log(`got ${x.length} events`));
Achieves the same. Notice share() to avoid duplicate click subscriptions - YMMV.
As Trevor mentioned, in RXJS 6 there is no official way but clearly you need to use debounce + buffer in order to achieve that result.
To make things properly, in Typescript and with Type Inference, I created a custom OperatorFunction called bufferDebounce that makes a lot easier to use and understand this operator.
The snippet with type inference
type BufferDebounce = <T>(debounce: number) => OperatorFunction<T, T[]>;
const bufferDebounce: BufferDebounce = debounce => source =>
new Observable(observer =>
source.pipe(buffer(source.pipe(debounceTime(debounce)))).subscribe({
next(x) {
observer.next(x);
},
error(err) {
observer.error(err);
},
complete() {
observer.complete();
},
})
// [as many sources until no emit during 500ms]
source.pipe(bufferDebounce(500)).subscribe(console.log)
You can try it in this working example: https://stackblitz.com/edit/rxjs6-buffer-debounce