RxJs - forkJoin with empty array - rxjs

I'm currently using forkJoin to wait for an array of Observable(s) to finish before pipe(ing) and tap(ping).
I noticed if the array is empty nothing is emitted and I cannot even tap. How do I solve this kind of problem? Should I just check if the array is empty?
myFirstFunction(...) {
const observables = ...
return forkJoin(observables)
}
mySecondFunction(...) {
return myFirstFunction().pipe(tap(() => ...))
}

That's because forkJoin requires all source Observables to emit at least one item and when there are no source Observables there's nothing to emit. However, forkJoin will still send the complete notification so you can use for example defaultIfEmpty operator to make sure it always emits at least one next.
forkJoin(observables).pipe(
defaultIfEmpty(null),
).subscribe(...);
Demo: https://stackblitz.com/edit/rxjs-kkd1qa?file=index.ts

Additionally to martin's answer.
I had 2 observables returning arrays and if one of them gives me an empty array, it did not wait for the other observable to finish and completed instantly. You can handle such cases as follows using defaultIfEmpty.
const response1: Observable<any[]> = this.service.getResponse(params1).pipe(defaultIfEmpty([]));
const response2: Observable<any[]> = this.service.getResponse(params2).pipe(defaultIfEmpty([]));
Observable.forkJoin(response1, response2).subscribe((response) => {
console.log(response);
}, () => {
console.log('handle error here');
}, () => {
console.log('completed');
});

Related

Why is a stopped BehaviorSubject halting execution in a pipe of RXJS?

I have the following code:
this.workingStore$.pipe(
filter((workingStores) => !!workingStores[docID]),
concatMap((workingStores) => {
console.log(
'returning from concatMap',
workingStores[docID].getInitialDataSet(),
);
return workingStores[docID].getInitialDataSet();
}),
filter((isSet) => {
console.log('looking for set', isSet);
return isSet;
}),
),
workingStores[docID].getInitialDataSet() returns an Observable. Because the pipes that set it to true complete, the BehaviorSubject gets isStopped: true internally. Once it becomes true, the filter no longer fires for isSet.
Shouldn't it just know to return the final value? It seems that's not the case so how would I wrote this so the last filter always runs? If I do the following, it works, but is awfully code smelly
concatMap((workingStores) => {
if (
workingStores[docID].getInitialDataSet().getValue() === true
) {
return of(true);
}
return workingStores[docID].getInitialDataSet();
}),
I am aware ReplaySubject will give values, even after stopped, but I don't want to emit old values to any subscriber.
ReplaySubject has a constructor that accepts the number of latest events to replay. If you provide 1 it will act similarly to your BehaviorSubject.

How can i execute asynchronous code when an RxJS observable complete?

I would like to execute code when the observable complete. In my code, i execute this:
compact(): Observable<FileManifest> {
return this.loadIndex().pipe(
mergeMap((index) => index.walk()),
map((entry) => entry.manifest),
notUndefined(),
writeAllMessages(this.newPath, ProtoFileManifest),
finalize(async () => {
await Promise.all([
promises.rm(this.journalPath, { force: true }),
promises.rm(this.manifestPath, { force: true }),
]);
await promises.rename(this.newPath, this.manifestPath);
}),
);
}
The problem is that the finalize method is made for synchronous code. When i execute asynchronous code like above, the code will be executed independently from the subscribe.
I would like this will be execute when disposing resource of the observable but i want that when i subscribe, i always receive the event.
How can i put asynchronous code in the finalize method ?
Thanks
Ulrich
One way to do it is to create three observables instead of trying to do it all
in one. Each will make up a link in the sequential async chain you want to
make.
In order for the side effects in the promise-based observables to be lazy, we use defer.
Note that the defer callback's return value can be an observable, or an
"ObservableInput", which is what RxJS calls values it knows how to turn
into observables. This value can be (among other things) a promise.
({
compact(): Observable<FileManifest> {
const writeToTempManifest$ = this.loadIndex().pipe(
mergeMap((index) => index.walk()),
map((entry) => entry.manifest),
notUndefined(),
writeAllMessages(this.newPath, ProtoFileManifest)
);
const removeOldManifest$ = defer(() =>
Promise.all([
promises.rm(this.journalPath, { force: true }),
promises.rm(this.manifestPath, { force: true }),
])
);
const renameNewManifest$ = defer(() =>
promises.rename(this.newPath, this.manifestPath)
);
return from([
writeToTempManifest$,
removeOldManifest$,
renameNewManifest$,
]).pipe(concatAll());
},
});
Note that each of these observables potentially emits something (though I'm not familiar with the API). The first emits whatever the writeAllMessages operator does, while the second and third emit the resolved values of their respective promises. In the case of the second one, that's a two element array from the Promise.all.
If you want to suppress an observable's emitted values while still keeping it open until it completes, you can create an operator that does just that:
const silence = pipe(concatMapTo(EMPTY));

how to unsubscribe a RXJS subscription inside the subscribe method?

I have some javascript:
this.mySubscription = someObservable.subscribe((obs: any) => {
this.mySubscription.unsubscribe();
this.mySubscription = undefined;
}
on execution, the console logs the error ERROR TypeError: Cannot read property 'unsubscribe' of undefined.
I wonder why I can not unsubscribe inside the subscribe lambda function. Is there a correct way to do so? I have read a bit about using dummy-subjects and completing them or using takeUntil/takeWhile and other pipe operators workArounds.
What is a correct way/workaround to unsubscribe a subscription inside the subscription's subscribe-function?
I am currently using a dummy subscription like so:
mySubscription: BehaviorSubject<any> = new BehaviorSubject<any>(undefined);
// when I do the subscription:
dummySubscription: BehaviorSubject<any> = new BehaviourSubject<any>(this.mySubscription.getValue());
this.mySubscription = someObservable.subscribe((obs: any) => {
// any work...
dummySubscription.next(obs);
dummySubscription.complete();
dummySubscription = undefined;
}, error => {
dummySubscription.error(error);
});
dummySubscription.subscribe((obs: any) => {
// here the actual work to do when mySubscription emits a value, before it should have been unsubscribed upon
}, err => {
// if errors need be
});
You shouldn't try to unsubscribe in the subscribe function.
You can unsubscribe with operators like take, takeWhile or takeUntil.
take
Use take(n) to unsubscribe after someObservable emits n times.
someObservable.pipe(
take(1)
).subscribe(value => console.log(value));
takeWhile
Use takeWhile to unsubscribe when an emitted value fails a condition.
someObservable.pipe(
takeWhile(value => valueIsSave(value))
).subscribe(value => console.log(value));
valueIsSave(value): boolean {
// return true if the subscription should continue
// return false if you want to unsubscribe on that value
}
takeUntil
Use takeUntil(obs$) to unsubscribe when the observable obs$ emits.
const terminate = new Subject();
someObservable.pipe(
takeUntil(terminate)
).subscribe(value => console.log(value));
unsub() {
terminate.next() // trigger unsubscribe
}
If you make your stream asynchronous, what you're doing should work. For example, this will not work:
const sub = from([1,2,3,4,5,6,7,8,9,10]).subscribe(val => {
console.log(val);
if(val > 5) sub.unsubscribe();
});
but this will work:
const sub2 = from([1,2,3,4,5,6,7,8,9,10]).pipe(
delay(0)
).subscribe(val => {
console.log(val);
if(val > 5) sub2.unsubscribe();
});
Because the JS event loop is fairly predictable (blocks of code are always run to completion), If any part of your stream is asynchronous, then you can be sure that your subscription will be defined before your lambda callback is invoked.
Should you do this?
Probably not. If your code relies on the internal (otherwise hidden) machinations of your language/compiler/interpreter/etc, you've created brittle code and/or code that is hard to maintain. The next developer looking at my code is going to be confused as to why there's a delay(0) - that looks like it shouldn't do anything.
Notice that in subscribe(), your lambda has access to its closure as well as the current stream variable. The takeWhile() operator has access to the same closure and the same stream variables.
from([1,2,3,4,5,6,7,8,9,10]).pipe(
takeWhile(val => {
// add custom logic
return val <= 5;
})
).subscribe(val => {
console.log(val);
});
takeWhile() can to anything that sub = subscribe(... sub.unsubscibe() ... ), and has the added benefit of not requiring you to manage a subscription object and being easier to read/maintain.
Inspired by another answer here and especially this article, https://medium.com/#benlesh/rxjs-dont-unsubscribe-6753ed4fda87, I'd like to suggest takeUntil() with following example:
...
let stop$: Subject<any> = new Subject<any>(); // This is the one which will stop the observable ( unsubscribe a like mechanism )
obs$
.pipe(
takeUntil(stop$)
)
.subscribe(res => {
if ( res.something === true ) {
// This next to lines will cause the subscribe to stop
stop$.next();
stop$.complete();
}
});
...
And I'd like to quote sentence RxJS: Don’t Unsubscribe from those article title mentioned above :).

WithLatestFrom($foo) not emitting when $foo emits, possibly because of the use of merge

I am implementing an observable which can be subscribed to before it is "assigned" Think of it like hoisting an observable definition so I dont have to worry about the order in which I create observables derived from other observables, I call it a ColdSubject.
ColdSubject works fine (I can add observables to it, and only when somebody subscribes to the ColdObservable do its operators get evaluated).
However withLatestFrom will never emit while waiting for obs$, despite the observable it's "waiting for" emitting to a subscriber several times!
export class ColdSubject<T> {
// If you subscribe to this before an observable has been added to $acc, you will be notified as soon as one is added, and if you subscribe to this after an observable is added to acc$ you will also be notified
public obs$: Observable<T>;
public acc$ = new BehaviorSubject<Observable<T>>(merge());
constructor() {
this.obs$ = this.acc$.pipe(switchMap(v => v));
}
addObservable(newObservable: Observable<T>) {
this.acc$.next(merge(this.acc$.getValue(), newObservable))
}
}
const foo = new ColdSubject<number>();
# I know this observable is waiting for withLatestFrom because "Tap yeet" is logged
of('yeet').pipe(
tap(v => console.log(`tap ${v}`)),
withLatestFrom(foo.obs$)
).subscribe(v => {
console.log(`WithLatestFrom ${v}`);
});
# This observable will begin emitting 5 seconds into the script, because I wait 5 seconds to subscribe to it
foo.addObservable(
interval(1000).pipe(
take(5),
tap(v => console.log(`Interval ${v}`))
)
);
# Subscribe 5 seconds into script start, so I know that my ColdSubject only evaluates its observables once they're subscribed to
setTimeout(
() => foo.obs$.subscribe(v => console.log(`Subscribe ${v}`)),
5000
);
Why does foo.obs$ emit several times, while the operation waiting for its latest value not emit?
Looking at the source code one can see that withLatestFrom is triggered by_next which is fired by the source Observable calling next:
protected _next(value: T) {
if (this.toRespond.length === 0) {
/**
* value - emitted by the source Observable
* ...this.values - emitted by the Observables passed to `withLatestFrom`
*/
const args = [value, ...this.values];
if (this.project) {
this._tryProject(args);
} else {
this.destination.next(args);
}
}
}
Your issue is that your source completes right away, while the Observable passed to withLatestFrom has not emitted yet. by the time foo.obs emits, your source Observable has long since completed.
What I would recommend of using in your case is combineLatest as demonstrated below:
combineLatest(of("yeet"), foo.obs$)
.pipe(
tap(v => console.log(`tap ${v}`)),
)
.subscribe(v => {});
of('yeet') emits and is complete so withLatestFrom will complete as the source is complete.
Change your subscription to
of('yeet').pipe(
tap(v => console.log(`tap ${v}`)),
withLatestFrom(foo.obs$)
).subscribe({ complete: () => console.log('yeet complete') });
and you will see it is infact complete.
https://stackblitz.com/edit/rxjs-6-opeartors-2nuam1?file=index.ts

switchMap does not seem to complete when the inner observable completes

I'm still a noob when it comes to RxJS but here's a JSBin of what I am trying to do.
https://jsbin.com/wusalibiyu/1/edit?js,console
I have an observable 'a' (in my case it's the current active connection) which emits a new object whenever the connection reconnects. It's an observable itself because it can re-emit a new value.
I now want an observable that completes whenever an action is executed on the current connection. That action notifies it is done when the observable for it completes. This is b.
The problem is that when the inner observable completes, the outer does not complete. How to make the outer observable complete ... . Is there a different operator I should be using in RxJS5?
If I understand your requirement correctly, you can "lift" the inner stream out using a materialize/dematerialize pair (note I refactored as well as part of my never ending war to get people to stop using Observable#create).
JsBin (excerpt below)
function b(a) {
// Emit and complete after 100 millis
return Rx.Observable.timer(100)
// Ignore any values emitted
.ignoreElements()
// Emit the value on start
.startWith(a)
.do(() => console.log('creating observable'))
.finally(() => console.log('b done'));
}
var a$ = Rx.Observable.from(['a', 'b'])
.finally(() => console.log('a done'));
var result$ = a$.switchMap(function(a) {
console.log('switching map for a to b', a);
// This "materializes" the stream, essentially it maps complete -> next
return b(a).materialize();
})
// This does the opposite, and converts complete events back,
// but since we are now in the outer stream
// this results in the outer stream completing as well.
.dematerialize()
.share();
result$.subscribe(function(value) {
console.log('value', value);
}, function(e) {
console.error('e', e);
}, function() {
console.log('completed!');
})
result$.toPromise().then(function(data) {
console.log('this should trigger!?', data);
}, function(e) {
console.error('boom', e.toString());
});

Resources