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)
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);
}
});
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
Take for example:
this.http.get('/getdata').pipe(delay(2000))
I would like this request to take a minimum of 2s to complete, but not any longer than it takes for the request to complete.
In other words:
if the request takes 1s to complete, I want the observable to complete in 2s.
if the request takes 3s to complete, I want the observable to complete in 3s NOT 5s.
Is there some other pipe other than delay() that can achieve this that I don't know about or is there a way to build a custom pipe for this if necessary?
The use case is to show a loader, however if the request completes too fast it doesnt look good when the loader just "flashes" for a split second
To answer the question as asked, you could simply use combineLatest() to combine a timer(2000) observable and the request observable, then just ignore the result from the timer observable. It works because combineLatest waits until all observables have emitted at least one value before emitting one itself.
combineLatest(this.http.get('/getdata'), timer(2000), x => x)
Thanks to GregL, I updated this to just use a forkJoin. This will get the latest value of the streams. But if you want to check it on every emmission, you can replace forkJoin with combineLatest and that will work too.
In my working example:
this.ibanSubscription = forkJoin({
iban: this.ibantobicService.getById(Iban),
timer: timer(1000) //so now the ajax call will take at least 1 second
}
).pipe(
map( (stream: any) => <BicModel>stream.iban),
switchMap( (bic: BicModel) => of(this.processIbanData(bic))),
catchError((error: any) => of(this.messageList.handleError(error))),
finalize(() => this.loadIbanToBicFinalize())
).subscribe();
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 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