Using takeUntil on inner observable - rxjs

How to write this snippet better?
What I'm trying to achieve is to start a new polling (and discard the previous one) each time eventGenerator$ emits something, but the polling can be discarded also by other notifier (anotherEvent$)
this.eventGenerator.asObservable()
.subscribe(event => {
if (this.polling$) {
this.polling$.unsubscribe();
}
this.polling$ = timer(0, 1000)
.pipe(
switchMap(() => this.service.getSomething())
takeUntil(this.anotherEvent$)
)
.subscribe();
})
One of the basic rules is to not subscribe to other subscription inside subscribe() method.
For this switchMap operator sounds like a good choice to chain it but if takeUntil is used and the anotherEvent$ emits it will unsubscribe the whole subscription and not only the polling part, so another emit of eventGenerator won't be handled anymore and will not create another timer.

You can just restructure your operators:
this.eventGenerator$.pipe(
switchMap(() => timer(0, 1000).pipe(
switchMap(() => this.service.getSomething()),
takeUntil(this.anotherEvent$),
),
).subscribe(...);

Related

Build a initializer with Rxjs

I have two promises that need to execute in order. After they successfully finished, I want to emit a value and want to share that with all current and future subscriptions
concat(
from(promise1),
from(promise2)
).pipe(
switchMap(() => of(value))
).share()
Is not working, as value gets emitted too early. Who can help?
EDIT 1:
I found a working solution. Are there better ways?
const initializer =
concat(from(promise1), from(promise2))
.pipe(
last(),
switchMap(() => of(value)),
shareReplay(1)
);
try Promise.all
from(Promise.all(p1, p2)).pipe(map(() => value))
or just use forkJoin instead of concat
Since order is important to you, you can simply chain your calls together using switchMap:
const initializer = defer(() => promise1()).pipe(
switchMap(val1 => promise2()),
map(val2 => '(value from initializer)'),
shareReplay(1)
);
We use defer to prevent the promise from executing until there is a subscriber.
switchMap will map the incoming emission (value from promise 1) to the promise 2. It will emit the resolved value from promise 2.
If you ever have more promises to add to the chain, you can simply add more switchMap:
const initializer = defer(() => promise1()).pipe(
switchMap(val1 => promise2()),
switchMap(val2 => promise3()),
switchMap(val3 => promise4()),
map(val4 => '(value from initializer)'),
shareReplay(1)
);
Here's a little StackBlitz sample.

use RXJS to unsubscribe when a Subject emits specific value

I think I should leverage RXJS for a particular use case. The use case is that I have a subscription that I want to live until a certain value is emitted from a Subject somewhere else.
Eg:
// The sub to unsub when a certain <value> is emitted from a Subject elsewhere.
this.someObservable.subscribe(() => ...)
// Somewhere in the code far, far away, this should kill the subscription(s) that cares about <value>
this.kill.next(<value>)
My go to approach to handle this is caching the subscriptions and then unsubscribing when this.kill.next(<value>) with the relevant <value> is called. Though, that is the imperative approach and feels like it can be done better via takeWhile or some other such technique. Perhaps I might need to merge someObservable with kill Subject ?
How can I leverage RXJS to handle this?
takeUntil is the operator you want
this.someObservable.pipe(
takeUntil(this.kill.pipe(filter(val => val === killValue)))
).subscribe(() => ...)
Once the kill observable emits the killValue it will pass the filter and emit to the takeUntil which unsubscribes the stream.
const { timer, fromEvent, Subject } = rxjs;
const { takeUntil, filter } = rxjs.operators;
const kill$ = new Subject();
kill$.subscribe(val => {
console.log(val);
});
timer(500, 500).pipe(
takeUntil(kill$.pipe(filter(val => val === 'kill')))
).subscribe(val => {
console.log(val);
});
document.getElementById('rnd').addEventListener('click', () => {
kill$.next(Math.random());
});
document.getElementById('kill').addEventListener('click', () => {
kill$.next('kill');
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.6.2/rxjs.umd.min.js"></script>
<button id="rnd">Emit random</button>
<button id="kill">Emit kill</button>

Implement a loop logic within an rxjs pipe

I have a class, QueueManager, which manages some queues.
QueueManager offers 3 APIs
deleteQueue(queueName: string): Observable<void>
createQueue(queueName: string): Observable<string>
listQueues(): Observable<string>: Observable`
deleteQueue is a fire-and-forget API, in the sense that it does not return any signal when it has completed its work and deleted the queue. At the same time createQueue fails if a queue with the same name already exists.
listQueues() returns the names of the queues managed by QueueManager.
I need to create a piece of logic which deletes a queue and recreates it. So my idea is to do something like
call the deleteQueue(queueName) method
start a loop calling the listQueues method until the result returned shows that queueName is not there any more
call createQueue(queueName)
I do not think I can use retry or repeat operators since they resubscribe to the source, which in this case would mean to issue the deleteQueue command more than once, which is something I need to avoid.
So what I have thought to do is something like
deleteQueue(queueName).pipe(
map(() => [queueName]),
expand(queuesToDelete => {
return listQueues().pipe(delay(100)) // 100 ms of delay between checks
}),
filter(queues => !queues.includes(queueName)),
first() // to close the stream when the queue to cancel is not present any more in the list
)
This logic seems actually to work, but looks to me a bit clumsy. Is there a more elegant way to address this problem?
The line map(() => [queueName]) is needed because expand also emits values from its source observable, but I don't think that's obvious from just looking at it.
You can use repeat, you just need to subscribe to the listQueues observable, rather than deleteQueue.
I've also put the delay before listQueues, otherwise you're waiting to emit a value that's already returned from the API.
const { timer, concat, operators } = rxjs;
const { tap, delay, filter, first, mapTo, concatMap, repeat } = operators;
const queueName = 'A';
const deleteQueue = (queueName) => timer(100);
const listQueues = () => concat(
timer(1000).pipe(mapTo(['A', 'B'])),
timer(1000).pipe(mapTo(['A', 'B'])),
timer(1000).pipe(mapTo(['B'])),
);
const source = deleteQueue(queueName).pipe(
tap(() => console.log('queue deleted')),
concatMap(() =>
timer(100).pipe(
concatMap(listQueues),
tap(queues => console.log('queues', queues)),
repeat(),
filter(queues => !queues.includes(queueName)),
first()
)
)
);
source.subscribe(x => console.log('next', x), e => console.error(e), () => console.log('complete'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.4/rxjs.umd.js"></script>

Tap after observable has been subscribed

Is there a way to add more operations to an observable that has already been subscribed? I tried the below code, which doesn't work, as the One more tap after subscribe part is not executed (code is here)
import { of, timer } from 'rxjs';
import { tap, map, take } from 'rxjs/operators';
const source = timer(1000, 1000);
//transparently log values from source with 'do'
const example = source.pipe(
take(3),
tap(val => console.log(`BEFORE MAP: ${val}`)),
map(val => val + 10),
tap(val => console.log(`AFTER MAP: ${val}`))
);
//'do' does not transform values
//output: 11...12...13...14...15
const subscribe = example.subscribe(val => console.log(val));
example.pipe(
tap(val => console.log(`One more tap after subscribe: ${val}`))
);
The use cas I have in mind is where for example I make an http call, and more than one service needs to be updated with the reponse of the call.
I will take this as what you ultimately want to achieve
The use cas I have in mind is where for example I make an http call, and more than one service needs to be updated with the reponse of the call.
const onExampleCalled=new Subject();
// listen to example called
onExampleCalled.subscribe(console.log)
example.pipe(tap(result=>onExampleCalled.next(result)).subscribe()
I am not quite sure what you try to achieve, but the pipe() function does not alter the source Observable. It just outputs a new Observable that results from the old one and the operators you aplied in the pipe(). Therefor your last line of code is like writing
5;
Meaning you set a value as a statement. Maybe you could reassign your Observable to itself after Transformation (although I am sure, that it would look quite ugly and will be hard to understand for others and should therefor be avoided).
example = example.pipe(
tap(val => console.log(`One more tap after subscribe: ${val}`))
);
Maybe you should go more into detail what you specific usecase is, so that we can find a cleaner solution.

Should I unsubscribe after a complete?

I have a quick question about observable.
I have the following observable:
getElevation(pos: Cartographic): Observable<Cartographic> {
return new Observable(observer => {
const promise = Cesium.sampleTerrain(this.terrainProvider, 11, Cesium.Cartographic(pos.longitude, pos.latitude))
Cesium.when(promise, (updatedPositions) => {
observer.next(updatedPositions);
observer.complete();
});
});
}
In a component I have:
this.service.getElevation(value).subscribe((e) => {});
My question is, this is a one shoot observable, so I complete just after, is the complete automatically close the subscription? or, do I also have to do this:
const sub = this.service.getElevation(value).subscribe((e) => {sub.unsubscribe();});
In your case you don't need to unsubscribe.
All Observers will automatically be unsubscribed when you call complete. That said, you may want to implement your consuming (component) code do handle the possibility that the implementation of the service may change in the future.
You could do this by using the take operator which will unsubscribe after the first value is emitted:
this.service.getElevation(value).pipe(take(1)).subscribe((e) => {});
You should not unsubscribe in a subscription, it the observable emits instantly then sub is undefined.
If you want a self unsubscribing observable you can use takeUntil
finalise = new Subject();
this.service.getElevation(value).pipe(takeUntil(finalise)).subscribe((e) => {
finalise.next();
finalise.complete();
});
Brief note:
Try to control the subscription with operators such as takeUntil.
You don’t need to unsubscribe yourself if the sender(Subject) completes.
For your case, since the sender returned by getElevation function completes itself after emitting a value one time, you don’t need to either use any operator or unsubscribe yourself to unsubscribe it.
All you have to do: this.service.getElevation(value).subscribe((v) => // do what you want);

Resources