Create a blocking behavior between subscriptions - promise

I have created an observable from a promise using defer.
let connect$ = defer(() => connectAsync());
This will execute the connectAsync function each time I subscribe to connect$.
But I need it to wait until the previous subscription has been completed until the new subscription starts.
Here is some code with comments to make the behavior that I want more clear.
connect$.subscribe(); // --|>
connect$.subscribe(); // --|>
connect$.subscribe(); // --|>
Is there any rxjs operator or subject I can use to achieve behavior?

The Short Answer:
No, there's no such operator in the RxJS library
The Long(er) Answer:
One of the cool things about the way RxJS curries operators is that it's fairly painless to create your own and RxJS will treat it the same way it handles any other operator.
For example:
function shareQueue<T>(): MonoTypeOperatorFunction<T>{
let buffer: Observable<T> = EMPTY;
return (s: Observable<T>) => defer(() => {
buffer = concat(
buffer.pipe(ignoreElements()),
s
).pipe(
share({
resetOnError: () => EMPTY;
resetOnComplete: () => EMPTY;
resetOnRefCountZero: () => EMPTY;
})
);
return buffer;
});
}
You can then use it like this to get (what I think) you're after:
const connect$ = defer(connectAsync).pipe(
shareQueue()
);
connect$.subscribe();
connect$.subscribe();
connect$.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>

Change dynamically the concurrency factor in mergeMap operator

I have an Observable, mySourceObs, and the following code
concurrencyFactor = 10;
mySourceObs.pipe(
mergeMap(data => generateAnotherObservable(data), concurrencyFactor)
).subscribe();
Is there a way to change dynamically the concurrencyFactor once the chain of Observables has been subscribed?
You can do it like this, but inner observable will restart when concurrencyFactor changes.
concurrencyFactor = new BehaviorSubject(10);
combineLatest(mySourceObs,concurrent).pipe(
switchMap(([data,concurrent]) => generateAnotherObservable(data), concurrent)
).subscribe();
concurrencyFactor.next(5)
I have figured out a way of controlling the concurrency of mergeMap with a combination of an external Subject, switchMap and defer.
This is the sample code
// when this Observable emits, the concurrency is recalculated
const mySwitch = new Subject<number>();
let myConcurrency = 1;
// this block emits five times, every time setting a new value for the concurrency
// and the making mySwitch emit
merge(interval(1000).pipe(map(i => i + 2)), of(1))
.pipe(
delay(0),
take(5),
tap(i => {
myConcurrency = i * 2;
console.log("switch emitted", i);
mySwitch.next(i);
})
)
.subscribe();
// source is an hot Observable which emits an incresing number every 100 ms
const source = new Subject<number>();
interval(100)
.pipe(take(100))
.subscribe(i => source.next(i));
// this is the core of the logic
// every time mySwitch emits the old subscription is cancelled and a new one is created
mySwitch
.pipe(
switchMap(() =>
// defer is critical here since we want a new Observable to be generated at subscription time
// so that the new concurrency value is actually used
defer(() =>
source.pipe(
mergeMap(
i =>
// this is just a simulation of a an inner Observable emitting something
interval(100).pipe(
delay(100),
map(j => `Observable of ${i} and ${j}`),
take(10)
),
myConcurrency
)
)
)
)
)
.subscribe({
next: data => console.log(data)
});

Invoke method when no observers for RxJs Subject

How to invoke a method when all the observers have unsubscribed from a subject.
Update
const alphaStore = new BehaviourSubject(0);
observer1 = alphaStore.subscribe(console.log);
observer2 = alphaStore.subscribe(console.log);
And when all of these observers unsubscribe. I want a method to be invoked. Like...
Observer1 unsubscribed
Observer2 unsubscribed
All observers left
What you describe already does the finalize() operator. Better said finalize() calls its callback when the chain disposes which means it's called when all observers unsubscribes, the chain completes or errors.
const subject = new Subject();
const shared = subject.pipe(
finalize(() => console.log('finalize')),
share(),
);
https://stackblitz.com/edit/rxjs-rebfba
When all observers unsubscribe share() unsubscribes from its source which triggers finalize().
Currently there's no way to distinguish why finalize() was invoked. See this issue https://github.com/ReactiveX/rxjs/issues/2823 and examples there on how to do it.
You can create a custom Observable, that will track the subscription count.
Heres a simple example:
let count = 0;
const tracked$ = new Observable(() => {
count++;
return ()=>{
count--;
if (count === 0) {
console.log('I am empty');
}
};
})
And then merge it with Observable that does actual work.
For simplicity sake, lets imagine its just a timer
// const tracked$ = ...
const data$ = Observable.timer(0, 5);
const result$ = data$
.merge(tracked$)
.take(5)
.subscribe(value => console.log('v:', value));
After 5 values were emitted -- it will log I am empty.
Heres a live example (with some rewrite and two subscriptions):
https://observable-playground.github.io/gist/4a7415f3528aa125fb686204041138cb
NOTE: this code uses rxjs-compat notation, which is easier to read. Above linked example uses .pipe notation, which is more common now.
Hope this helps.

Resources