What is RxJS (redux-observable) alternative to takeLatest from redux-saga? - rxjs

If new action is received by epic, stop processing old action(s).
A little bit of context: I have an epic, that emits few delayed actions.
I need to cancel them, if new action is received by epic.
Redux-saga effect that does exactly what I want:
takeLatest

That's exactly what .switchMap operator does:
Projects each source value to an Observable which is merged in the output
Observable, emitting values only from the most recently projected Observable.
In this example you can see that previous interval is no longer processing after new click arrives:
const clicks = Rx.Observable.fromEvent(document, 'click');
const result = clicks.switchMap((ev) => Rx.Observable.interval(1000));
result.subscribe(x => console.log(x));
<script src="https://unpkg.com/rxjs/bundles/Rx.min.js"></script>
<button>send new click</button>

Related

Is the observable setup async?

Lets consider the following example:
// Subject sources have been created from Subjects
const one$ = scheduled([firstSubjectSource$, secondSubjectSource$], asyncScheduler)
.pipe(
mergeAll(),
share(),
);
const two$ = scheduled([thirdSubjectSource$, fourthSubjectSource$], asyncScheduler)
.pipe(
mergeAll(),
share(),
);
const final$ = scheduled([one$, two$], asyncScheduler)
.pipe(
combineLatestAll(),
map(() => { /* Some mapping */ }),
);
return final$;
The final$ is created, returned and can be subscribed to.
I have observed that the marble tests work perfectly, i.e, by the time the tests run all the observables have been setup and subscribed to correctly. But in the actual executing environment (iOS 15 JavascriptCore), this doesn't seem to be the case. Values are forwarded to the ...SubjectSource$ observables after subscription to final$, but final$ never emits anything. Tapping console logs in the one$, two$ shows that they also don't emit anything. My current hypothesis is that the internal subscription process hasn't finished. I have combed through some rxjs code but it doesn't look like the subscription process is async.
AFAIK, the asyncScheduler shouldn't make the internal subscription async. It should only affect how the input values are processed and forwarded.
If the return statement is changed to below, then everything works fine. However, putting an arbitrary wait time doesn't seem like the correct thing to do either.
setTimeout(() => cb(final$), 100);
Is the internal setup of observables one$, two$ and final$ async or sync?
Is there an event that I 'need to'/'can' wait on before returning final$ for use?
How do I make sure that the observables are actually ready for use before I return it?

Rxjs buffer the emitted values for specified time after source emitted values

I have a source$ observable collecting a stream of data if there are some events trigger. I want to collect these data which occurred in a specified time into array.
const eventSubject = new Subject();
eventSubject.next(data);
const source$ = eventSubject.asObservable();
source$.pipe(takeUntil(destroyed$)).subscribe(
data => {
console.log(data);
}
);
The above source$ handle emitted data immediately.
Now i want to improve this that wait for a few seconds and collect all data happened in that specified time and emit once. So i modify to use with bufferTime like below:
const source$ = eventSubject.asObservable();
source$.pipe(takeUntil(destroyed$), bufferTime(2000)).subscribe(
data => {
console.log(data);
}
);
After testing with bufferTime, I found that it emits every 2s even source is not receiving data. If source not receiving data, it emit empty object.
What i want is only when source$ receiving data, then start to buffer for 2s, then emit value. If source$ not receiving data, it shouldn't emit anything.
I checked the bufferWhen, windowWhen, windowTime not all meeting my requirements. They are emitting every time interval specified.
Is there have other operator can do what i want?
Thanks a lot.
You can just add a filter operator to ignore the empty object emission
const source$ = eventSubject.asObservable();
source$.pipe(takeUntil(destroyed$), bufferTime(2000),filter(arr=>arr.length)).subscribe(
data => {
console.log(data);
}
);
I'd go for connect(shared$ => ...) and buffer(signal$).
I think something along these lines:
source$.pipe(
connect(shared$ => shared$.pipe(
buffer(shared$.pipe(
debounceTime(2000)
))
))
)
connect creates a shared observable so that you can have multiple subscriptions on the source without actually opening those subscriptions to it.
In there I run a buffer, whose selector is the debounceTime of the same source, so that it debounces for that much (i.e. will emit the array when source$ doesn't emit for more than 2 seconds)
Maybe what you need is throttleTime(2000, { leading: false, trailing: true }) instead of debounceTime. It depends on your use case.
The optimal solution for this case is to use buffer operator with a notifier of debouceTime operator.
For example :
const source$ = eventSubject.asObservable();
source$
.pipe(
// buffer: accumulate emitions to an array,until closing notifier emits. (closing notifier is the argument below)
buffer(
// debounceTime : will grab the emit that afterwards 2 seconds has passed without another emit.
$source.pipe(debounceTime(2000))
).subscribe(
// will return the data that has been emitted througout the 2 seconds in a form of an array , where each item in the array is the emits, by the order they were triggered.
data => {
console.log(data);
}
);
buffer:
Buffers the source Observable values until closingNotifier emits.
debounceTime:
Emits a notification from the source Observable only after a particular time span has passed without another source emission.
This solution in contrast of the filter operator solution, will not keep an interval/timer alive.
And its pretty clean and elegant IMO.

How to add a delay between repeat operations in rxjs

I am building a page that displays a bunch of pdf documents to the user. I am currently using the repeat operator from the 'rxjs/operators' library in order to force the refresh of a documents page.
My code is below
import { map, repeat } from 'rxjs/operators';
...
// Fetch Document type Filter.
this.firebaseService
.documentAdminAccessList()
.snapshotChanges()
.pipe(
map(changes => changes.map(c => ({ value: c.payload.val() }))),
repeat(5)
However, what is happening is that sometimes that the link to the documents page is not appearing in time. I fear that the repeat(5) is being executed too fast. How can I add a 500 millisend delay between each repeat operation?
Update (Jan 14, 2021), I am still seeing this issue. The documents link on the Menu bar is still delayed; only after the initial nav bar loads.
Below is my code:
Rafi. Could the 'take' be wrong?
// Fetch Document type Filter.
this.firebaseService
.documentAdminAccessList()
.snapshotChanges()
.pipe(
map(changes => changes.map(c => ({ value: c.payload.val() }))),
repeatWhen(x=> x.pipe(delay(250), take(5)))
)
Link to Navigation Menu Bar
repeatWhen
Returns an Observable that mirrors the source Observable with the
exception of a complete. If the source Observable calls complete, this
method will emit to the Observable returned from notifier. If that
Observable calls complete or error, then this method will call
complete or error on the child subscription. Otherwise this method
will resubscribe to the source Observable it returns an Observable
that mirrors the source Observable with the exception of a complete. (Rxjs Docs)
Consider utilizing it combined with delay operator so that when the source Observable completes, the Observable returned by the method repeatWhen was passed to (which could be the source Observable it was provided with) would get resubscribed repeatedly (with a delay of X ms between retries) resulting in the source Observable getting resubscribed each time it emits (the Observable repeatWhen was provided with) as shown below:
repeatWhen(x=> x.pipe(delay(5000)))
Looking at your question it seems like you wish to limit repeat count, this can easily be achieved by using take(5) operator as demonstrated below:
of(SOME_VALUE)
.pipe(
repeatWhen(x => x.pipe(delay(500), take(5)))
)
.subscribe(x => {
console.log(x)
})

How to convert an Observable to a ReplaySubject?

Here is what I'm doing now to convert an Observable to a ReplaySubject:
const subject = new Rx.ReplaySubject(1);
observable.subscribe(e => subject.next(e));
Is this the best way to make the conversion, or is there a more idiomatic way?
You can use just observable.subscribe(subject) if you want to pass all 3 types of notifications because a Subject already behaves like an observer. For example:
let subject = new ReplaySubject();
subject.subscribe(
val => console.log(val),
undefined,
() => console.log('completed')
);
Observable
.interval(500)
.take(5)
.subscribe(subject);
setTimeout(() => {
subject.next('Hello');
}, 1000)
See live demo: https://jsbin.com/bayewo/2/edit?js,console
However this has one important consequence. Since you've already subscribed to the source Observable you turned it from "cold" to "hot" (maybe it doesn't matter in your use-case).
It depends what do you mean by 'convert'.
If you need to make your observable shared and replay the values, use observable.pipe(shareReplay(1)).
If you want to have the subscriber functionality as well, you need to use the ReplaySubject subscribed to the original Observable observable.subscribe(subject);.
Like the first answer, as subject is also an observer.
const subject = new Rx.ReplaySubject(1);
observable.subscribe(subject);

RxJs Observable interval until reached desired value

I want to poll for changes and when a desired value is reached the Observable should complete (or wait until timeout). Right now I use the filter which works fine to wait until the desired value is reached. But I want the Observable to push events while waiting for this value.
For example, I wait for the status 'success' and until the status changes to 'success' the status 'testing' is returned from my service. But since the filter is waiting for 'success', 'testing' never returns.
My code right now:
return Observable
.interval(this.POLL_TIMEOUT)
.flatMap(() => this.getSingleProjectStatus(projectId, repoName))
.filter(data => this.finishedStatus(data.status))
.take(1)
.timeout(this.MAX_TIMEOUT, Observable.throw(new Error('Timeout')));
You probably want takeWhile instead of filter.
return Observable
.interval(this.POLL_TIMEOUT)
.flatMap(() => this.getSingleProjectStatus(projectId, repoName))
.takeWhile(data => this.finishedStatus(data.status))
.timeout(this.MAX_TIMEOUT, Observable.throw(new Error('Timeout'));
Note the above takes all except the last event, if you want the last event too you'll need to be a little trickier.
const source = Observable.interval(this.POLL_TIMEOUT)
.flatMap(() => this.getSingleProjectStatus(projectId, repoName))
.share();
source
.takeUntil(source.filter(data => this.finishedStatus(data.status)))
.timeout(this.MAX_TIMEOUT, Observable.throw(new Error('Timeout'));
In this case you are taking all the results until another Observable emits, the other Observable in this case is simply the source filtered such that it only emits success events.
JsBin: http://jsbin.com/sojosuhune/edit?html,js,console,output

Resources