The behavior of delay has changed in RxJs 7. While I understand the reasoning for the change, it was useful for a demo project that I use to simulate over-the-wire API delays. In RxJs 6, the code below would only log to the console after the 5 second delay, but in 7 it is immediately logged (7 no longer waits for delays on an empty observable). Is there a way to replicate the following in RxJs 7?
import { EMPTY } from 'rxjs';
import { delay } from 'rxjs/operators';
EMPTY.pipe(delay(5000)).subscribe({
complete: () => {
console.log('complete');
},
});
See the Stackblitz examples below.
RxJs 7 (no delay): https://stackblitz.com/edit/rxjs-yx19nb?file=index.ts
RxJS 6 (5 second delay): https://stackblitz.com/edit/rxjs-8rmhov?file=index.ts
Just use a timer instead.
timer(5000).pipe(
ignoreElements()
).subscribe(...);
Related
I'm writing an angular15 app with a youtube player component in it, i'm trying to work with rxjs but i think that i have one issue that i got wrong, the mergeMap. i'm really new to rxjs so sorry for any mistakes
I have 2 subscriptions, one for if youtube library as finished loading, and the other if the youtube player is ready.
first lets look just at the interval:
this.YTSubscription=interval(100).pipe(
exhaustMap((x, y)=>{
this.currentTimeSubject.next(this.player.getCurrentTime());
this.isPlayingSubject.next(this.player.getPlayerState() === YT.PlayerState.PLAYING);
this.isMutedSubject.next(this.player.isMuted());
this.volumeSubject.next(this.player.getVolume());
return of(true);
}),
).subscribe({next: (data )=>{
},
error: (err)=> {
this.YTSubscription?.unsubscribe();
}
});
this works fine, it runs in intervals on 100ms and i use exhaustMap to make sure that the next iteration will be executed only if the previous one completed in case when i'll add more calculations it may take more than 100 ms.
next i want in the interval to check if youtube is loaded, for that i have the observable isYouTubeLoaded, so i tried using mergeMap for this.. i guess this is not the right way? but it still worked:
this.YTSubscription=interval(100).pipe(
mergeMap(x => this.isYouTubeLoaded),
exhaustMap((x, y)=>{
if (!x) {
return of(false);
}
...
now x inside exahustMap contains the isYouTubeLoaded and this does the job.
now i have another observable that i want to check and only if both of them are true to run the interval, if not to wait for the next iteration, this is where i get lost because if i add another mergeMap i can't see both values in exhaustMap.
so from reading some more i assume that i'm not supposed to use mergeMap at all, maybe filter ? but i still have no clue how to do that with 2 observables.
any ideas?
I'm not entirely sure, what you want to do, but I'll try to answer this part of your question:
now i have another observable that i want to check and only if both of them are true to run the interval, if not to wait for the next iteration, this is where i get lost because if i add another mergeMap i can't see both values in exhaustMap.
combineLatest([src1, src2]).pipe( // check both
filter(([ok1, ok2]) => ok1 && ok2), // only if both are true
switchMap(() => timer(...) // run the timer
).subscribe(...);
#churill really helped, in the end i need two pipes and not 3 but the implementation is the same, still marking his answer as the correct one, just showing here the resulting code:
this.YTSubscription=combineLatest([interval(100), this.isYouTubeLoaded]).pipe(
map(([intr, loaded])=>(loaded)),
filter((loaded)=> (loaded)),
exhaustMap(()=>{
try {
if (this.player.getPlayerState() === YT.PlayerState.UNSTARTED) {
return of(false);
}
} catch (e) {
return of(false);
}
this.currentTimeSubject.next(this.player.getCurrentTime());
this.isPlayingSubject.next(this.player.getPlayerState() === YT.PlayerState.PLAYING);
this.isMutedSubject.next(this.player.isMuted());
this.volumeSubject.next(this.player.getVolume());
return of(true);
}),
).subscribe({next: (isUpdated)=>{
},
error: (err)=> {
console.error(err);
}
});
Starting with an empty observable I have a steady stream of non-rxjs events that I need to throttle with rxjs but i cant find a way to create a throttled output. In my use case I do not know when the first value will arrive and nor can I determine the frequency of new values arriving.
https://stackblitz.com/edit/rxjs-behaviorsubject-simpleexample-etebvz?file=index.ts
I was expecting this example to work and display the values added with next() to be throttled 1s apart but its not working.
import { BehaviorSubject, interval } from 'rxjs';
import { tap, map, throttle } from 'rxjs/operators';
const subject = new BehaviorSubject(1);
const example = subject.pipe(
throttle(ev => interval(1000)),
tap((ev) => console.log(ev))
)
example.subscribe();
example.next(2);
example.next(3);
example.next(4);
example.next(5);
example.next(6);
I cant find any online examples to match this (apparently) simple use case and working with rxjs feels unintuitive to achieve this. Any help is much appreciated.
throttleTime allows you to specify a number of milliseconds to wait before emitting again.
const example = subject.pipe(
throttleTime(1000),
tap((ev) => console.log(ev))
)
Stackblitz
I recommend checking out the operator decision tree and clicking through to see what options are available:
I have one existing Observable
I want to ignore values
that occur too frequently
According to rxjs marbles documentation the current behaviour for the sync groupings is the following:
'(ab)-(cd)': on frame 0, emits a and b then on frame 50, emits c and d
From the docs:
While it can be unintuitive at first, after all the values have synchronously emitted time will progress a number of frames equal to the number of ASCII characters in the group, including the parentheses
Ok, but how do I test an observable like this (using marbles or any other technique):
const observable$ = of(1, 2).concat(of(3, 4).delay(20));
Are there any workarounds?
There is a similar question on Stack Overflow but there is no answer on 'How to actually work around it and test this kind of observable'.
Thanks!
For my project I migrated to rx-sanbox where sync grouping works correct and it solved my problem.
So, in rx-sandbox this is correct:
'(ab)-(cd)': on frame 0, emits a and b then on frame 20, emits c and d
I don't know what version of RxJS you're using because you're mixing prototypical and pipable operators but it looks like RxJS 5.5.
In RxJS 5.X it's a bit clumsy. You could rewrite your test like this:
import { of } from 'rxjs/observable/of';
import { TestScheduler } from 'rxjs/testing/TestScheduler';
import { assert } from 'chai';
import 'rxjs/add/operator/concat';
import 'rxjs/add/operator/delay';
const scheduler = new TestScheduler((actual, expected) => {
console.log(actual, expected);
return assert.deepEqual(actual, expected);
});
const observable$ = of('a', 'b').concat(of('c', 'd').delay(50, scheduler));
scheduler
.expectObservable(observable$)
.toBe('(ab)-(cd|)');
scheduler.flush();
See live demo (open console): https://stackblitz.com/edit/rxjs5-marble-test?file=index.ts
You know this test passes because it doesn't throw any error. Try changing any of the delays or values of next emissions and it'll throw an error.
Also have a look at this answer: How do I test a function that returns an observable using timed intervals in rxjs 5?
However, I'd strongly recommend upgrading to RxJS 6 because it makes everything much easier with cold and hot "creation" functions where you could just use const observable$ = cold('(ab)-(cd|)') to create the same sequence as you're doing with of(...).concat(...).
Testing in RxJS 6:
https://github.com/ReactiveX/rxjs/blob/master/doc/marble-testing.md
https://github.com/ReactiveX/rxjs/blob/master/doc/internal-marble-tests.md
I want my observable to fire immediately, and again every second. interval will not fire immediately. I found this question which suggested using startWith, which DOES fire immediately, but I then get a duplicate first entry.
Rx.Observable.interval(1000).take(4).startWith(0).subscribe(onNext);
https://plnkr.co/edit/Cl5DQ7znJRDe0VTv0Ux5?p=preview
How can I make interval fire immediately, but not duplicate the first entry?
Before RxJs 6:
Observable.timer(0, 1000) will start immediately.
RxJs 6+
import {timer} from 'rxjs/observable/timer';
timer(0, 1000).subscribe(() => { ... });
RxJs 6. Note: With this solution, 0 value will be emitted twice (one time immediately by startWith, and one time by interval stream after the first "tick", so if you care about the value emitted, you could consider startWith(-1) instead of startWith(0)
interval(100).pipe(startWith(0)).subscribe(() => { //your code });
or with timer:
import {timer} from 'rxjs/observable/timer';
timer(0, 100).subscribe(() => {
});
With RxJava2, there's no issue with duplicated first entry and this code is working fine:
io.reactivex.Observable.interval(1, TimeUnit.SECONDS)
.startWith(0L)
.subscribe(aLong -> {
Log.d(TAG, "test"); // do whatever you want
});
Note you need to pass Long in startWith, so 0L.
RxJava 2
If you want to generate a sequence [0, N] with each value delayed by D seconds, use the following overload:
Observable<Long> interval(long initialDelay, long period, TimeUnit unit)
initialDelay - the initial delay time to wait before emitting the first value of 0L
Observable.interval(0, D, TimeUnit.SECONDS).take(N+1)
You can also try to use startWith(0L) but it will generate sequence like: {0, 0, 1, 2...}
I believe something like that will do the job too:
Observable.range(0, N).delayEach(D, TimeUnit.SECONDS)
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