Concat Array of Observables and have a single subscription output - javascript

I have several cases on my software where I have an array of observables and I need to execute them in order. Having the next subscription to happen only after the previous is complete.
So Im using the concat operator. It works great, however its subscription gets triggered every time one of the Observables gets completed, and I need to have it be triggered only after everything is complete.
concat(
of(1, 2, 3).pipe(delay(3000)),
// after 3s, the first observable will complete and subsquent observable subscribed with values emitted
of(4, 5, 6).pipe(delay(3000)),
)
// log: 1,2,3,4,5,6
.subscribe((v) => {
// Needs to be triggered once after everything is complete
console.log(v);
});
I need a way to pipe this observable so the subscription gets triggered only once after everything is complete, the value of the subscription is not important in this case, so it can be omitted.
If possible the values could be made available in a form of an array inside the subscription context.

Collect the values in an array with toArray.
import { toArray } from 'rxjs/operators';
concat(
of(1, 2, 3).pipe(delay(3000)),
of(4, 5, 6).pipe(delay(3000)),
).pipe(
toArray()
).subscribe(v => console.log(v)); // log: [1,2,3,4,5,6]
Or if you don't need the response use the complete callback like in #Willem's solution.

Pipe the results into a finalize():
Call a function when observable completes or errors
See https://www.learnrxjs.io/operators/utility/finalize.html
Subscribe to the complete event:
.subscribe({
complete: () => { ... }
})
Use forkJoin(), especially if you want the final values:
When all observables complete, emit the last emitted value from each.
https://www.learnrxjs.io/operators/combination/forkjoin.html

Related

RxJS waiting for array elements to complete not working as intended

I'm expecting the following RxJS behavior: for every element in the source array, execute an action (the part commented out) which needs to be awaited to complete, and only then fetch next element in source array, wait again and so on.
But the behavior I get instead, is all elements in the source array are fetched at the same time, then after the delay they are retried all again etc.
import { from, defer, delay, repeat, tap } from 'rxjs';
const source$ = from([1, 2, 3])
const actions$ = source$.pipe(
tap((t) => console.log(t))
// ... action that takes long and needs to be waited for, before going to the next element in source$
)
const timedExecution$ = defer(() => actions$).pipe(
delay(3000),
repeat(3)
)
timedExecution$.subscribe();
I also tried another way, with timer:
import { from, tap, timer } from 'rxjs';
const source$ = from([1, 2, 3])
const actions$ = source$.pipe(
() => timer(0, 3000),
tap((t) => console.log(t))
// actionThatTakesLong() action that takes long and needs to be waited for, before going to the next element in source$
)
actions$.subscribe();
Here, it emits one at a time, but sometimes the actionThatTakesLong() takes longer than the arbitrary 3000 MS value of the timer, and i need it to wait until its done, instead of a hardcoded value of waiting.
Thanks for any hints in advance
Your source Observable is from() which is a synchronous Observable that emits array items one after another immediately on subscription. It doesn't (and can't) care what happens with the values in the chain.
delay() will take each value and delay it by a certain time but it doesn't (and can't) care whether the previous values have reached your observer. It just takes each value and delays it by 3s without waiting for the previous delay to complete so in your case it appears like all values were emitted at the same time.
What you want to do instead is adding concatMap() operator that will wait until the nested delayed Observable completes:
from([1, 2, 3])
.pipe(
concatMap(value => of(value).pipe(delay(3000))),
)
.subscribe(...);
FYI, the second option you are mentioning does something very different than you think:
const actions$ = source$.pipe(
() => timer(0, 3000),
tap(() => ...),
);
This is actually replacing the source Observable from() with a different Observable timer(0, 3000). You're basically using approach used for creating custom operators https://rxjs.dev/guide/operators#creating-new-operators-from-scratch.

Wait till all Observables are completed

I have few Observables like this one in my code.
this.server.doRequest().subscribe(response => console.log(response)
error => console.log(error),
() => {
console.log('completed');
});
There could be any number of these Observables,
so I need to write a function that checks if each Observable is done otherwise waits till each is finished.
I'm assuming I can create an array push every new Observable there and when it's completed remove it by index. But is it good solution?
Where I want to use it. For example I have a page where user upload photos any amount asynchronously and then he press Finish button. Once he pressed Finish button I need to wait till ALL dynamically created Observables are completed.
you should use higher order observables for this, your exact use case will dictate the exact operator, but forkJoin seems a good candidate:
forkJoin(
this.server.doRequest1(),
this.server.doRequest2(),
this.server.doRequest3(),
this.server.doRequest4()
).subscribe(vals => console.log('all values', vals));
forkJoin won't emit till all innter observables have completed. making it the operator of choice for waiting for multiple observables to complete. You can also feed it an array of observables. There are multiple other operators that may fulfill your case too, such as concat, merge, combineLatest or a few others.
edit based on more details:
in the use case described in your update, you'll still want to use a higher order observable, but forkjoin is not what you want. you'll want to use a local subject to accomplish the goal as wanting to kick off each observable as it is selected and waiting for them all to be done complicates things a little (but not too much):
suppose you had a template like:
<button (click)="addPhoto()">Add Photo</button>
<button (click)="finish()">Finish</button>
where the add photo button gets the users photo and all that, and finish is your completion, you could have a component like this:
private addPhoto$ = new Subject();
constructor() {
this.addPhoto$.pipe(
mergeMap(() => this.uploadPhoto()),
).subscribe(
(resp) => console.log('resp', resp),
(err) => console.log('err', err),
() => console.log('complete')
);
}
private uploadPhoto() {
// stub to simulate upload
return timer(3000);
}
addPhoto() {
this.addPhoto$.next();
}
finish() {
this.addPhoto$.complete();
}
if you run this code, you'll see that the photo adds will emit in the subscribe handler as they complete, but complete will only fire once all the photo uploads have completed and the user has clicked finish.
here is a stackblitz demonstrating the functionality:
https://stackblitz.com/edit/angular-bsn6pz
I'd create a dictionary (in javascript that would be a JSON with observable names as boolean properties) where you push each observable on "create" and a method which should execute on completion of each observable, which will iterate through that dictionary and if all completed do something.
That will ensure parallelism and final execution after all completed.
var requests = {
doRequest1: false,
doRequest2: false,
doRequest3: false
};
var checkIfCAllCompleted = name => {
requests[name] = true;
for (var property in requests) {
if (object.hasOwnProperty(property)) {
if (!property) {
return;
}
}
}
// all properties are true - do something here
console.log("here");
}
this.server.doRequest1().then(() => checkIfCAllCompleted("doRequest1"));
this.server.doRequest2().then(() => checkIfCAllCompleted("doRequest2"));
this.server.doRequest3().then(() => checkIfCAllCompleted("doRequest3"));

Cancel subscription, when subscribing two observables to each other

I have two observables, that never complete (they are event handlers). Every time that observable A emits, I want to discard all emissions of B, wait for B to emit a value, and do something with A's emission; ignore further B emissions. If A emits a second time, while waiting for B, I want to cancel waiting.
Currently I have the following code:
obsA$.subscribe(value => {
obsB$.pipe(take(1)).subscribe(_ => {
if (value) {
// do stuff with value
}
});
});
This does not cover the case when: A emits once, then A emits again, and then B emits a value, the 1st subscription to B should be cancelled, and only the second subscription should execute.
How should I approach this issue? Is there some better / clearer way to write this?
ObservableA is a navigation event (navigation to a new address), and ObsB is from an animation event, that happens after navigation. When navigating to a new address, I want to wait for the animation to complete, and then do something.
It looks like you're describing switchMap that always subscribes to the Observable returned from its callback and unsubscribes from the previous one.
obsA$
.pipe(
switchMap(v => obsB$.pipe(mapTo(v))), // pass through only the original emission from obsA$
)
.subscribe(resultFromA => ...);

How can I use Observables instead of Promises?

I have a service with some methods, most of them require a certain callback to be completed before it can do its stuff. With Promises, in pseudo, it is very easy to do this:
ready = http.get(stuff); // Returns a promise, resolves after a while
methodOne() { // methods sometimes called before promise resolves
this.ready.then(_ => {
// doStuff
});
}
methodTwo() {
return this.ready.then(d => {
// doOtherStuff
});
}
Basically I need to do the stuff, only when i'm sure the service is ready.
I actually only need to check if it's ready (what methodOne is doing, just illustrating with methodTwo, that it's easy to more stuff as well).
I want to try and go all in on Observables, but for this specific case, I find it really hard to compete with a similar solution for Observables.
Promises will remember the value and know if it got resolved. An Observable is somewhat more complex and it seems that creating this same flow is troublesome. I need whatever is subscribing to the Observable, to known when it's ready. Some times the method is called early - before the Observable emits and sometimes late, after the Observable already emitted.
I have this right now, but it doesn't seem to work:
this.ready$ = someObservable // Will fire after a litle while but never finish - i only need the first to check though.
.publishReplay(1).refCount(); // Trying to replay if subscription comes after emit.
this.ready$.subscribe(_ => {
// This will be called
});
methodOne() {
this.ready$.subscribe(_ => {
// Not called
});
};
Perhaps i misunderstood the use of publishReplay and refCount?
I think what you're looking for is AsyncSubject. It mimics the promises behavior very well. Here is the description:
The AsyncSubject is a variant where only the last value of the
Observable execution is sent to its observers, and only when the
execution completes.
Here is how it can be used in your case:
subject = new AsyncSubject();
ready = streamOfData(stuff).first().subscribe(subject);
methodOne() {
return this.subject.asObservable();
}
The subject subscribes to the underlying observable returned by the first operator and waits until it's complete. It collects all the subscribers but doesn't send any values to them. As soon as the underlying observable completes it remembers the value and sends it to the collected subscribers. All new future subscribers will be immediately passed this stored resolved value.
Here is the simple example that demonstrates that you can subscribe before or after the observable completes:
const subject = new AsyncSubject();
const o = subject.asObservable();
o.subscribe((v) => {
console.log(v);
});
interval(500).first().subscribe(subject);
setTimeout(() => {
o.subscribe((v) => {
console.log(v);
});
}, 2000);

How onComplete actually works in RxJS

Observables complete naturally if they are constructed from finite data.
import {Observable, Subject} from "rx";
let stream0$ = Observable.of("1", "2", "3");
let stream1$ = stream0$.map(x => x);
stream1$.subscribe(
(val) => { console.log("onNext", val) },
(err) => { console.log("onError", err) },
() => { console.log("onCompleted") }
);
// onNext 1
// onNext 2
// onNext 3
// onCompleted
Or don't if not. But what about observables subscribed on subjects? For example:
import {Observable, Subject} from "rx";
let subj$ = new Subject();
let stream1$ = subj$.map(x => x);
stream1$.subscribe(
(val) => { console.log("onNext", val) },
(err) => { console.log("onError", err) },
() => { console.log("onCompleted") }
);
subj$.onNext("foo");
// onNext foo
"onCompleted" is not logged though source is ended. Can we pass this "end" event to stream1$ somehow. I've found no information about this important stuff in docs. It would be great to see a diagram like here Hot and Cold observables : are there 'hot' and 'cold' operators? to nail that event flow.
With a subject, you are completely in control. Rx.Subject implements the observer interface and it is that observer interface that you use when you call onNext.
A subject
assume that all serialization and grammatical correctness are handled by the caller of the subject.
That means among other things that it is up to you to signal completion and error. To signal completion, use onCompleted. FYI, here is the aforementioned grammar :
This grammar allows observable sequences to send any amount (0 or more) of onNext messages to the subscribed observer instance, optionally followed by a single success (onCompleted) or failure (onError) message.
The single message indicating that an observable sequence has finished
ensures that consumers of the observable sequence can
deterministically establish that it is safe to perform cleanup
operations.
A single failure further ensures that abort semantics can be
maintained for operators that work on multiple observable sequences.
NOTE : for RxJS v5, the observer interface has changed, cf. new interface
How Complete and Error events actually work in RxJS is a following research I've made.
Quoting myself from there.
Completion should not be taken as "event at the end of the program" or something. It's a very specific thing. There are only three possible ways for stream to complete: 1) Be a finite Observable and complete naturally 2) Be a Subject and get imperative onCompleted() call. 3) Get completion event from upstream. Any form of process termination / unsubscription do not complete streams.
Completion terminates stream. Nothing happens in a stream after completion.
Completion is passed downstream. Observable derived from Subject completes if/when Subject completes. Subject subscribed to Observable completes if/when Observable completes.

Categories

Resources