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));
Related
Is there an alternative to doing .subscribe() in map?
return this.http.patch<any>(URL, { test: true }).pipe(
tap(_ => this.nextSomething$.next({})),
filter(_ => this.filter),
map(resp => {
this.someObservable({ message: 'do' }).subscribe()
return resp
})
)
I tried doing switchMap and returning the previous response but my observable does not complete.
switchMap(prevResp =>
this.someObservable({ message: 'do' }) }).pipe(map( _ => prevResp))
)
Thank you
If your observable is not completing when you switch to using the flattening operator then that means the target observable in that operator isn't completing. So you might want to check what's going on in someObservable that causes it to never complete. It is likey that behavior isn't desirable.
If you know that someObservable will emit at least once, then you can add the first operator to the inner pipe method:
switchMap(prevResp =>
this.someObservable({ message: 'do' }) }).pipe(
first(),
map( _ => prevResp)
)
)
If you don't care what someObservable does - you don't want to wait for it just want it to execute, then wrap the observable in firstValueFrom. It converts an observable into a promise that emits the first result. This code smells, but it should do the trick.
this.http.patch<any>(URL, { test: true }).pipe(
tap(() => this.nextSomething$.next({})),
filter(() => this.filter),
tap(() => firstValueFrom(this.someObservable({ message: 'do' })))
)
Maybe, you don't care to wait for this observable to emit, but you're still looking for an orderly execution of someObservable. In that case you can use a subject that emits every time you want it called, and use concatMap to ensure the execution is performed in an orderly fashion.
private readonly someObservableRequest = new Subject<string>();
constructor() {
this.someObservableRequest.pipe(
concatMap((message) => this.someObservable({ message }))
).subscribe(); // be kind, please unsubscribe
}
someMethod() {
return this.http.patch<any>(URL, { test: true }).pipe(
tap(_ => this.nextSomething$.next({})),
filter(_ => this.filter),
tap(() => this.someObservableRequest('do'))
);
}
If I understand your point right, you have to execute consecutively 2 Observables.
In this case you need yo use one so called "higher order" operators, i.e. either concatMap or switchMap (there are other "higher order" operators but I feel they do not apply to your case).
The code then would look like this
myNewOservable this.http.patch<any>(URL, { test: true }).pipe(
tap(_ => this.nextSomething$.next({})),
filter(_ => this.filter),
// an higher order operator returns an Observable
concatMap(resp => {
return this.someObservable({ message: 'do' })
})
)
return myNewOservable
Now you can subscribe to myNewOservable.
In my example I have used concatMap which ensures that a value notified by the upstream Observable is processed through the downstream pipeline before processing the next value from upstream.
I could have used also switchMap, which has a slightly different behavior: as soon as a new value is notified by the upstream Observable, any downstream processing is terminated.
In this case, given that http emits only one value and then completes the 2 operators act the same.
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.
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 :).
I have an observable stream set up as below. I have an interval that is polling every two seconds. I then switchMap that to make two dependent API calls (mocked here with 'of's). After, I want to use distinctUntilChanged to make sure the final object is different. The only thing is that distinctUntilChanged doesn't fire.
I'm assuming it has SOMETHING to do with the fact that we are creating new streams and therefore never collects two objects to compare, but I don't fully understand.
interval(2000).pipe(
switchMap(() => loadData()),
)
.subscribe(res => console.log(res)); // { name: 'test' } is printed every two seconds
function loadData() {
return of('API call').pipe(
mergeMap(numb => of({ name: 'test' })),
distinctUntilChanged((prev, cur) => {
console.log('CompareFn'); // This will never fire.
return JSON.stringify(prev) === JSON.stringify(cur)})
);
}
Stackblitz: https://stackblitz.com/edit/rxjs-ko6k3c?devtoolsheight=60
In this case, I would like there to only be a single value ever printed from the next handler as distinctUntilChanged should stop all values after the first.
Would appreciate an explanation as to why this isn't working as I would expect it to.
the problem is that your distinctUntilChanged is operating on the inner observable, not the outter... you need to do this
interval(2000).pipe(
switchMap(_ => loadData()),
distinctUntilChanged((prev, cur) => {
console.log('CompareFn');
return JSON.stringify(prev) === JSON.stringify(cur);
})
)
.subscribe(res => console.log(res));
function loadData() {
return of('API call').pipe(
mergeMap(numb => of({ name: 'test' }))
);
}
in your prior set up, only one value ever reached distinctUntilChanged as the interval switched into a new observable via switch map.
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');
});