How to do onething after another in rxjs - rxjs

I find myself wanting to do this, which feels like it ought to be wrong.
this.isLoading = true;
this.service.getFirstValue().subscribe((response: firstValueType) => {
this.firstValue = response;
this.service.getSecondValue(this.firstValue).subscribe((response: secondValueType) => {
this.secondValue = response;
this.isLoading = false
});
});
What are you supposed to do?

you can use switchMap. Also embedded subscribe is bad practice, supposed to be avoided
this.isLoading = true;
this.service
.getFirstValue()
.pipe(
switchMap(response => {
this.firstValue = response;
return this.service.getSecondValue(this.firstValue);
})
)
.subscribe(response => {
this.secondValue = response;
this.isLoading = false;
});
PS fix the code, subscribe should be outside of pipe

The solution I figured out works fine for me in my project was using the following parts of rxjs:
ConnectableObservable
switchMap
tap
const { Subject } = rxjs;
const { publishReplay, tap, switchMapTo } = rxjs.operators;
// Simulates your service.getFirstValue, ...
const source1$$ = new Subject();
const source2$$ = new Subject();
const source3$$ = new Subject();
// Publish and Replay the last value to be able to connect (make it hot) and always get the lates value
const source1$ = source1$$.pipe(publishReplay(1));
const source2$ = source2$$.pipe(publishReplay(1));
const source3$ = source3$$.pipe(publishReplay(1));
// Connect to make the observable hot
source1$.connect()
source2$.connect()
source3$.connect()
source1$.pipe(
tap(v => console.log('tap source1$: ', v)),
switchMapTo(source2$),
tap(v => console.log('tap source2$: ', v)),
switchMapTo(source3$),
tap(v => console.log('tap source3$: ', v))
).subscribe()
source3$$.next('value 1');
source2$$.next('value 2');
source1$$.next('value 3');
source2$$.next('value 4');
source2$$.next('value 5');
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.3/rxjs.umd.min.js"></script>
I am not sure if this fits your exactly requirements. But imo this is a at least close szenario like yours. If you want give me feedback and I try to adapt :)

Nested subscribe should be avoided.
In this case I would go like this
this.isLoading = true;
this.service.getFirstValue().pipe(
// this tap is necessary only if you use this.firstValue somewhere else in the code
// otherwise you can skip it and go directly to concatMap
tap((response_1: firstValueType) => this.firstValue = response_1),
// concatMap is usually the preferred operator to use when concatenating http calls
// read the article I have linked below
concatMap(response_1 => this.service.getSecondValue(response_1)),
tap(response_2 => {
this.secondValue = response_2;
this.isLoading = false;
})
).subscribe()
You can get some more inspiration on how to deal with http calls and rxJs reading this article.

Related

Have multiple operations on a single event

Trying to have a event triggering multiple switchMap with the initial event data.
Each actions creates a promise to some transform that is then written in the file system. The actions are independent and unrelated, but uses the same data, just for different purpose, so they should not be merged.
Currently using taps instead of switchMap, that can lead to multiple event running the at the same time.
const SomeApiCall = () => {return {some: 'data'} }
const AllowsDoAction = () => {console.log('Parsing API and writting some things to FS -- PLACEHOLDER')}
const SomeTimeDoThisActionTo = () => {console.log('Parsing API and writting some other things to fs, by asking more data from the API and first, so it is long thing to do, so a new event can arrive first -- PLACEHOLDER')}
const deepEqual = (prev, cur) => prev === cur // normally a proper deepEqual...
const taps = [tap(AllowsDoAction)];
if (someCondition) taps.push(SomeTimeDoThisActionTo)
const observable = timer(0, 500).pipe(
exhaustMap(SomeApiCall),
distinctUntilChanged((prev, cur) => deepEqual(prev, cur))
...taps
);
I would return the filesystem write observables (edit 2)
const SomeApiCall = () => {
return of({ some: 'data' });
};
const AllowsDoAction = () => {
console.log('Parsing API and writting some things to FS -- PLACEHOLDER');
return timer(100).pipe(map(() => 'write 1 finished'));
};
const SomeTimeDoThisActionTo = () => {
console.log(
'Parsing API and writting some other things to fs, by asking more data from the API and first, so it is long thing to do, so a new event can arrive first -- PLACEHOLDER'
);
return timer(1000).pipe(map(() => 'write 2 finished'));
};
And then use concatMap to wait for all filesystem operation to complete.
const deepEqual = (prev, cur) => prev === cur; // normally a proper deepEqual...
const taps = [(AllowsDoAction)];
const someCondition = true;
if (someCondition) {
taps.push(SomeTimeDoThisActionTo);
}
const reduxStorageEvent$ = of('replace this with real event');
const observable = merge(timer(0, 500), reduxStorageEvent$).pipe(
exhaustMap(SomeApiCall),
distinctUntilChanged((prev, cur) => deepEqual(prev, cur)),
// use switchMap to cancel previous writes (edit 2)
// await latest write operations, before starting new writes
concatMap((someData) => {
const writes = taps.map((tapFx) => {
return tapFx(someData);
});
// wait for all writes
return forkJoin(...writes);
})
);
concatMap is like a queue. The first one in this queue has to finish before the second one can start.

Any reason to use shareReplay(1) in a BehaviorSubject pipe?

I'm using a library that exposes data from a service class using a pretty common BehaviorSubject pattern. The only notable difference with the implementation and what I have seen/used myself is the addition of a pipe with a shareReplay(1) operator. I'm not sure if the shareReplay is required. What effect, if any, does the shareReplay have in this case?
// "rxjs": "^6.3.0"
this.data = new BehaviorSubject({});
this.data$ = this.data.asObservable().pipe(
shareReplay(1)
)
Note: I've read a number of articles on shareReplay, and I've seen questions about different combinations of shareReplay and Subject, but not this particular one
Not in your example but imagine if there was some complex logic in a map function that transformed the data then the share replay would save that complex logic being run for each subscription.
const { BehaviorSubject } = rxjs;
const { map, shareReplay } = rxjs.operators;
const bs$ = new BehaviorSubject('initial value');
const obs$ = bs$.pipe(
map(val => {
console.log('mapping');
return 'mapped value';
}),
shareReplay({bufferSize:1, refCount: true})
);
obs$.subscribe(val => { console.log(val); });
obs$.subscribe(val => { console.log(val); });
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.1/rxjs.umd.min.js"></script>
Compare without the share, the map happens twice.
const { BehaviorSubject } = rxjs;
const { map } = rxjs.operators;
const bs$ = new BehaviorSubject('initial value');
const obs$ = bs$.pipe(
map(val => {
console.log('mapping');
return 'mapped value';
})
);
obs$.subscribe(val => { console.log(val); });
obs$.subscribe(val => { console.log(val); });
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.1/rxjs.umd.min.js"></script>
With this pattern (use of shareReplay(1)), the service protects itself from the user using the next() function of the BehaviorSubject while sending the last value of the BehaviorSubject (which would not have been the case without shareReplay(1)).

RxJS: MergeMap with Preserving Input order

Requirement:
urls = [url1, url2, url3]
Fire all 3 urls parallely and paint the Dom in the sequnce of the urls list
ex: Finished order of urls = [url3, url1, url2]
when url1 finishes Immediately render the DOM, without waiting for url2
If url2, url3 finishes before url1, then store url2, url3 and paint the DOM after url1 arrives
Paint the DOM with order [url1, url2, url3]
My Work using promises:
// Fired all 3 urls at the same time
p1 = fetch(url1)
p2 = fetch(url2)
p3 = fetch(url3)
p1.then(updateDom)
.then(() => p2)
.then(updateDom)
.then(() => p3)
.then(updateDom)
I wanted to do the same thing in Observables.
from(urls)
.pipe(
mergeMap(x => fetch(x))
)
To fire them parallely I used merge map, but how can I order the sequence of the results?
The best way to preserve order with async tasks like this is with concatMap.
The problem is that if we apply this alone, we lose the parallelisation. If we were to do something like this:
from(urls)
.pipe(
concatMap(x => fetch(x))
);
the second request is not fired until the first is complete.
We can get around this by separating out the map into its own operator:
from(urls)
.pipe(
map(x => fetch(x)),
concatMap(x => x)
);
The requests will all be fired at the same time, but the results will be emitted in request order.
See Adrian's example adapted to use this approach below:
const { from } = rxjs;
const { concatMap, map } = rxjs.operators;
function delayPromise(value, delay) {
return new Promise(resolve => setTimeout(() => resolve(value), delay));
}
var delay = 3;
from([1, 2, 3]).pipe(
map(x => delayPromise(x, delay-- * 1000)),
concatMap(x => x)
).subscribe(result => { console.log(result); });
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.4.0/rxjs.umd.min.js"></script>
I couldn't find anything that preserves the order so I came up with something a bit convoluted.
const { concat, of, BehaviorSubject, Subject } = rxjs;
const { delay, filter } = rxjs.operators;
const parallelExecute = (...obs$) => {
const subjects = obs$.map(o$ => {
const subject$ = new BehaviorSubject();
const sub = o$.subscribe(o => { subject$.next(o); });
return { sub: sub, obs$: subject$.pipe(filter(val => val)) };
});
const subject$ = new Subject();
sub(0);
function sub(index) {
const current = subjects[index];
current.obs$.subscribe(c => {
subject$.next(c);
current.obs$.complete();
current.sub.unsubscribe();
if (index < subjects.length -1) {
sub(index + 1);
} else {
subject$.complete();
}
});
}
return subject$;
}
parallelExecute(
of(1).pipe(delay(3000)),
of(2).pipe(delay(2000)),
of(3).pipe(delay(1000))
).subscribe(result => { console.log(result); });
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.4.0/rxjs.umd.min.js"></script>
You can form a sequence with fetch and paint then forkJoin/Promise.all them
p1 = fetch(url1)
p2 = fetch(url2)
p3 = fetch(url3)
forkJoin(
from(p1).pipe(tap(_=>paint dom...))
from(p1).pipe(tap(_=>paint dom...))
from(p1).pipe(tap(_=>paint dom...))
).subscribe()

Do something if RxJs subject's refCount drops to zero

I'm working on a service layer that manages subscriptions.
I provide subject-backed observables to consumers like this:
const subject = new Subject();
_trackedSubjects.push(subject);
return subject.asObservable();
Different consumers may monitor the channel, so there may be several observables attached to each subject.
I'd like to monitor the count of subject.observers and if it ever drops back to 0, do some cleanup in my library.
I have looked at refCount, but this only is available on Observable.
I'd love to find something like:
subject.onObserverCountChange((cur, prev) =>
if(cur === 0 && prev !== 0) { cleanUp(subject) }
)
Is there a way to automatic cleanup like this on a subject?
Instead of using Subject - you should probably describe setup/cleanup logic when creating observable. See the example:
const { Observable } = rxjs; // = require("rxjs")
const { share } = rxjs.operators; // = require("rxjs/operators")
const eventSource$ = Observable.create(o => {
console.log('setup');
let i = 0
const interval = setInterval(
() => o.next(i++),
1000
);
return () => {
console.log('cleanup');
clearInterval(interval);
}
});
const events$ = eventSource$.pipe(share());
const first = events$.subscribe(e => console.log('first: ', e));
const second = events$.subscribe(e => console.log('second: ', e));
setTimeout(() => first.unsubscribe(), 3000);
setTimeout(() => second.unsubscribe(), 5000);
<script src="https://unpkg.com/rxjs#6.2.2/bundles/rxjs.umd.min.js"></script>

Create an Rx.Subject using Subject.create that allows onNext without subscription

When creating an Rx.Subject using Subject.create(observer, observable), the Subject is so lazy. When I try to use subject.onNext without having a subscription, it doesn't pass messages on. If I subject.subscribe() first, I can use onNext immediately after.
Let's say I have an Observer, created like so:
function createObserver(socket) {
return Observer.create(msg => {
socket.send(msg);
}, err => {
console.error(err);
}, () => {
socket.removeAllListeners();
socket.close();
});
}
Then, I create an Observable that accepts messages:
function createObservable(socket) {
return Observable.fromEvent(socket, 'message')
.map(msg => {
// Trim out unnecessary data for subscribers
delete msg.blobs;
// Deep freeze the message
Object.freeze(msg);
return msg;
})
.publish()
.refCount();
}
The subject is created using these two functions.
observer = createObserver(socket);
observable = createObservable(socket);
subject = Subject.create(observer, observable);
With this setup, I'm not able to subject.onNext immediately (even if I don't care about subscribing). Is this by design? What's a good workaround?
These are actually TCP sockets, which is why I haven't relied on the super slick websocket subjects.
The basic solution, caching nexts before subscription with ReplaySubject:
I think all you wanted to do is use a ReplaySubject as your observer.
const { Observable, Subject, ReplaySubject } = Rx;
const replay = new ReplaySubject();
const observable = Observable.create(observer => {
replay.subscribe(observer);
});
const mySubject = Subject.create(replay, observable);
mySubject.onNext(1);
mySubject.onNext(2);
mySubject.onNext(3);
mySubject.subscribe(x => console.log(x));
mySubject.onNext(4);
mySubject.onNext(5);
Results in:
1
2
3
4
5
A socket implementation (example, don't use)
... but if you're looking at doing a Socket implementation, it gets a lot more complicated. Here is a working socket implementation, but I don't recommend you use it. Rather, I'd suggest that you use one of the community supported implementations either in rxjs-dom (if you're an RxJS 4 or lower) or as part of RxJS 5, both of which I've helped work on.
function createSocketSubject(url) {
let replay = new ReplaySubject();
let socket;
const observable = Observable.create(observer => {
socket = new WebSocket(url);
socket.onmessage = (e) => {
observer.onNext(e);
};
socket.onerror = (e) => {
observer.onError(e);
};
socket.onclose = (e) => {
if (e.wasClean) {
observer.onCompleted();
} else {
observer.onError(e);
}
}
let sub;
socket.onopen = () => {
sub = replay.subscribe(x => socket.send(x));
};
return () => {
socket && socket.readyState === 1 && socket.close();
sub && sub.dispose();
}
});
return Subject.create(replay, observable);
}
const socket = createSocketSubject('ws://echo.websocket.org');
socket.onNext('one');
socket.onNext('two');
socket.subscribe(x => console.log('response: ' + x.data));
socket.onNext('three');
socket.onNext('four');
Here's the obligatory JsBin

Resources