Determine what caused an observable to complete - rxjs

I have the following basic Observable setup:
fooObservable
.pipe(takeUntil(barObservable))
.subscribe({
complete: () => {
// Do something only if fooObservable completes, not if barObservable emits.
}
})
As you can see in the comment above, I want to "do something" in the completion block only if the observable is completed due to fooObservable completing. If it completes due to barObservable emitting, I do not want to do anything. How can I determine what caused this observable to complete to write this logic?

There may be a way to address your question using the tap operator just before the takeUntil operator and passing into the complete function of the tap operator the logic you want to run when foo completes.
This is the code and this is a stackblitz
const foo = of('foo').pipe(
delay(200)
)
const bar = of('bar').pipe(
delay(300)
)
foo
.pipe(
tap({
complete: () => console.log('DONE because FOO completed')
}),
takeUntil(bar)
)
.subscribe({
complete: () => console.log('don not do anything since you are here because BAR emitted')
})
If your requisite requires strictly that you run the code you want to run in the complete function passed to the subscribe method, then you can do something similar but you would be required to use some variable to store the state. Such variable should be set withing the complete function of the tap operator

The first idea I got:
let barEmitted = false;
fooObservable
.pipe(takeUntil(barObservable.pipe(tap(() => barEmitted = true))))
.subscribe({
complete: () => {
if(!barEmitted) { do your stuff}
}
})
a bit hacky, but I've never encountered your situation before :)

If fooObservable terminates because of barObservable and you want to execute some logic when barObservable emits the first value you can move this logic to barObservable :
barObservable
.pipe(take(1))
.subscribe(e => /*Do something only if barObservable completes*/)
fooObservable
.pipe(takeUntil(barObservable))
.subscribe({
complete: () => {
// continue to do nothing
}
})

Related

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 :).

Why is finalize lifted in RxJS?

I am having trouble understanding the finalize operator in RxJS. Let me demonstrate this on an example:
of(null).pipe(
tap({ complete: () => console.log('tap 1 completes') }),
finalize(() => console.log('finalize')),
tap({ complete: () => console.log('tap 2 completes') })
).subscribe({ complete: () => console.log('subscribe completes') });
I would expect the finalize callback to be executed before the second tap. That's not happening, though. Rather the above code produces the following output:
tap 1 completes
tap 2 completes
subscribe completes
finalize
Looking at the implementation I believe the operator is passed (lifted) through the whole observable chain to always be applied at its end. So now I end up with two questions:
What's the rationale behind this design decision? Can you give some explanation on why this is a desireable / advantageous property?
Is there a different operator or other solution to execute code on complete and on error, but in order (i.e. before the second tap in the above example) rather than at the end of the observable chain?
It's important to be aware that finalize() and tap() work very differently. tap() is triggered by next, error and complete notifications while finalize() is only triggerd on chain ubsubscription. In other words finalize() is very similar to using:
const subscription = $source.subscribe();
// This will be always triggered after all `tap()`s
subscription.add(() => console.log('same as finalize()'));
So you can't make finalize() to be invoked before tap(). Also, be aware that finalize() is invoked also when you manually unsubscribe liek the following:
subscription.unsubscribe(); // will always invoke `finalize()` but never `tap()`
One possible solution could be implementing you own finalize() variant that knows the reason why it's being called: https://github.com/martinsik/rxjs-extra/blob/master/doc/finalizeWithReason.md (see source code)
Also note, that https://github.com/ReactiveX/rxjs/pull/5433 will have no affect on your use-case.
That's the whole principle of the finalize operator. To emit only -after- the source observable completes. Which does mean, after all the complete subscriptions have been handled, counting the tap complete. From the docs:
Returns an Observable that mirrors the source Observable, but will call a specified function when the source terminates on complete or error.
Now you could place the finalize in an inner observable, but I suppose you wouldn't like the order then either
of(null).pipe(
tap({ complete: () => console.log('tap 1 completes') }),
concatMap((resp) => of(resp).pipe(
finalize(() => console.log('finalize'))
)),
tap({ complete: () => console.log('tap 2 completes') })
).subscribe({ complete: () => console.log('subscribe completes') });
This will make the finalize execute before the first and last tap, this is because of the complete object you pass into tap. If you just pass in a function in the first tap, it will have the right order.
Another way could be the usage of concat:
concat(
of(null).pipe(
tap({complete: () => console.log('tap 1 completes' ) }),
finalize(() => console.log('finalize'))
),
EMPTY.pipe(
tap({complete: () => console.log('tap 2 completes' ) })
)
).subscribe({ complete: () => console.log('subscribe completes') });
But this kinda prevents you from accessing what ever the first observable emitted. So, basically, I don't think there is a proper solution to what you are asking :)

How can I create an observable like the one returned by defer using Observable.create?

I'm trying to create an observable with behavior similar to what is returned by defer, but using create method. So I try:
const obs = Observable.create(function(observer) {
from(fetch('https://jsonplaceholder.typicode.com/todos/1').then(console.log('fetch done'))).subscribe(observer)
})
setTimeout(()=>obs.subscribe((resp)=>console.log(resp.statusText)), 5000)
But when I run it in node It just prints "fetch done" (on subscription, after 5 seconds as expected) but waits forever there.
When I wrap the from(..) in setImmediate() or setTimeout(,0), then sometimes it prints both messages ("Fetch done", "OK") and exits and sometimes it only prints "fetch done" and waits forever.
code:
const obs = Observable.create(function(observer) {
setTimeout(()=>from(fetch('https://jsonplaceholder.typicode.com/todos/1').then(console.log('fetch done'))).subscribe(observer), 0)
})
setTimeout(()=>obs.subscribe((resp)=>console.log(resp.statusText)), 5000)
Why this happens? What am I doing wrong?
First thing is that Observable.create is deprecated and you should use just new Observable():
When creating Observables like this you have access to observer object where you can call next() and complete() so in your case it would be like this:
new Observable(observer => {
fetch('https://jsonplaceholder.typicode.com/todos/1').then(response => {
observer.next(response);
observer.complete();
});
});
I think this should work as well:
new Observable(observer => {
const sub = from(fetch('https://jsonplaceholder.typicode.com/todos/1')).subscribe(observer);
// Return tear-down function so you can abort request.
return () => sub.unsubscribe();
});
Obviously, this is over complicated and if you just want to wrap a Promise with Observable you can use just from().
Promise.then takes a callback function, and you need to return the argument to chain it, so it becomes then(result => { console.log('fetch done', result); return result; })

How to test an inner Observable that will not complete?

I'm using jest to test a redux-observable epic that forks off an inner observable created using Observable.fromEvent and listens for a specific keypress before emitting an action.
I'm struggling to test for when the inner Observable does not receive this specific keypress and therefore does not emit an action.
Using jest, the following times out:
import { Observable, Subject } from 'rxjs'
import { ActionsObservable } from 'redux-observable'
import keycode from 'keycode'
const closeOnEscKeyEpic = action$ =>
action$.ofType('LISTEN_FOR_ESC').switchMapTo(
Observable.fromEvent(document, 'keyup')
.first(event => keycode(event) === 'esc')
.mapTo({ type: 'ESC_PRESSED' })
)
const testEpic = ({ setup, test, expect }) =>
new Promise(resolve => {
const input$ = new Subject()
setup(new ActionsObservable(input$))
.toArray()
.subscribe(resolve)
test(input$)
}).then(expect)
// This times out
it('no action emitted if esc key is not pressed', () => {
expect.assertions(1)
return testEpic({
setup: input$ => closeOnEscKeyEpic(input$),
test: input$ => {
// start listening
input$.next({ type: 'LISTEN_FOR_ESC' })
// press the wrong keys
const event = new KeyboardEvent('keyup', {
keyCode: keycode('p'),
})
const event2 = new KeyboardEvent('keyup', {
keyCode: keycode('1'),
})
global.document.dispatchEvent(event)
global.document.dispatchEvent(event2)
// end test
input$.complete()
},
expect: actions => {
expect(actions).toEqual([])
},
})
})
My expectation was that calling input$.complete() would cause the promise in testEpic to resolve, but for this test it does not.
I feel like I'm missing something. Does anyone understand why this is not working?
I'm still new to Rx/RxJS, so my apologies if the terminology of this answer is off. I was able to reproduce your scenario, though.
The inner observable (Observable.fromEvent) is blocking the outer observable. The completed event on your ActionsObservable doesn't propagate through until after the inner observable is completed.
Try out the following code snippet with this test script:
Run the code snippet.
Press a non-Escape key.
Nothing should be printed to the console.
Select the "Listen for Escape!" button.
Press a non-Escape key.
The keyCode should be printed to the console.
Select the "Complete!" button.
Press a non-Escape key.
The keyCode should be printed to the console.
Press the Escape key.
The keyCode should be printed to the console
The onNext callback should print the ESC_PRESSED action to the console.
The onComplete callback should print to the console.
document.getElementById('complete').onclick = onComplete
document.getElementById('listenForEsc').onclick = onListenForEsc
const actions = new Rx.Subject()
const epic = action$ =>
action$.pipe(
Rx.operators.filter(action => action.type === 'LISTEN_FOR_ESC'),
Rx.operators.switchMapTo(
Rx.Observable.fromEvent(document, 'keyup').pipe(
Rx.operators.tap(event => { console.log('keyup: %s', event.keyCode) }),
Rx.operators.first(event => event.keyCode === 27), // escape
Rx.operators.mapTo({ type: 'ESC_PRESSED' }),
)
)
)
epic(actions.asObservable()).subscribe(
action => { console.log('next: %O', action) },
error => { console.log('error: %O', error) },
() => { console.log('complete') },
)
function onListenForEsc() {
actions.next({ type: 'LISTEN_FOR_ESC' })
}
function onComplete() {
actions.complete()
}
<script src="https://unpkg.com/rxjs#5.5.0/bundles/Rx.min.js"></script>
<button id="complete">Complete!</button>
<button id="listenForEsc">Listen for Escape!</button>
Neither the switchMapTo marble diagram nor its textual documentation) clearly indicate what happens when the source observable completes before the inner observable. However, the above code snippet demonstrates exactly what you observed in the Jest test.
I believe this answers your "why" question, but I'm not sure I have a clear solution for you. One option could be to hook in a cancellation action and use takeUntil on the inner observable. But, that might feel awkward if that's only ever used in your Jest test.
I can see how this epic/pattern wouldn't be a problem in a real application as, commonly, epics are created and subscribed to once without ever being unsubscribed from. However, depending on the specific scenario (e.g. creating/destroying the store multiple times in a single application), I could see this leading to hung subscriptions and potential memory leaks. Good to keep in mind!

Resources