After reading a large number of posts on stack overflow, I am still very confused about the difference between Observable and Subject in Rx.
Basically, most people point out that the key difference between the two is that "The Subject class inherits both Observable and Observer, in the sense that it is both an observer and an observable", and it can be demonstrated by the following code snippet:
var subject = new Rx.Subject();
var subscription = subject.subscribe(
function (x) { console.log('onNext: ' + x); },
function (e) { console.log('onError: ' + e.message); },
function () { console.log('onCompleted'); }
);
subject.onNext(1);
// => onNext: 1
subject.onNext(2);
// => onNext: 2
subject.onCompleted();
// => onCompleted
I understand that in the following part of the code, subject is being the role of an observable that can be subscribed to:
var subscription = subject.subscribe(
function (x) { console.log('onNext: ' + x); },
function (e) { console.log('onError: ' + e.message); },
function () { console.log('onCompleted'); }
);
What really confuses me is this part:
subject.onNext(1);
// => onNext: 1
subject.onNext(2);
// => onNext: 2
subject.onCompleted();
// => onCompleted
It looks like here it demonstrates that subject can be the role of an observer as well, but my understanding is that here subject is also feeding values/events (sending 1, 2 and complete event). So why is the subject considered the role of an observer in the code above?
Thanks a lot.
The subject is considered as taking the role of an observer in the referenced code precisely because onNext and onCompleted are being called on it. An Observer is defined as an object on which you can call onNext and onCompleted (as well as onError.)
Related
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 :).
Consider the following:
a$ = someObservable$.pipe(
switchMap(data => liveForEver$)
);
a$.subscribe();
a$.unsubscribe();
Now, liveForEver$ as the name suggests is subscribed to by other parts of the code. Could it be that a$ will stay subscribed after a$ is unsubscribed because switchMap returns a 'living' observable?
When an operator is defined, it usually has behavior to unsubscribe to child subscriptions when it is unsubscribed to. If you make a custom operator and fail to do this, then you'll likely create memory leaks. Consider the following custom operator:
function timesTwo(input$: Observable<number>): Observable<number> {
return new Observable<number>(observer => {
input$.subscribe({
next: val => observer.next(val * 2),
complete: () => observer.complete(),
error: err => observer.error()
});
return {
// I should $input.unsubscribe()
unsubscribe: () => {/*Do Nothing*/}
}
});
}
function timesTwoPipeable<T>(): MonoTypeOperatorFunction<T> {
return input$ => timesTwo(input$);
}
Here I've created my own custom rxjs operator that multiplies a stream of inputs by two. So 1:
const subscription = interval(1000).pipe(map(x => x * 2))
.subscribe(console.log);
setTimeout(() => subscription.unsubscribe(), 5000);
and 2:
const subscription = timesTwo(interval(1000))
.subscribe(console.log);
setTimeout(() => subscription.unsubscribe(), 5000);
and 3:
const subscription = interval(1000).pipe(timesTwoPipeable())
.subscribe(console.log);
setTimeout(() => subscription.unsubscribe(), 5000);
All have identical outputs to the console, but 2 and 3 both subscribe to the interval stream and then do not unsubscribe to it. So the second two quietly create a memory leak. You could test this yourself by changing interval(1000) to interval(1000).pipe(tap(_ => console.log("Still Alive"))) in all three examples.
All the built-in RxJS operators clean up after themselves. If you build your own, be sure to do the same!
Something I noticed in your question is that you tried to unsubscribe to an observable. I'm surprised that didn't create an error.
My inderstanding is that:
a$.subscribe();
a$.unsubscribe();
should be:
const sub = a$.subscribe();
sub.unsubscribe();
I want to create a function that will make AJAX requests to backend. And if this function is called many times at the same time, then it should not make many identical requests to the server. It must make only 1 request.
For example:
doAJAX('http://example-1.com/').subscribe(res => console.log); // must send a request
doAJAX('http://example-1.com/').subscribe(res => console.log); // must NOT send a request
doAJAX('http://example-2.com/').subscribe(res => console.log); // must send a request, bacause of different URL
window.setTimeout(() => {
doAJAX('http://example-2.com/').subscribe(res => console.log); // must send a request because too much time has passed since the last request
}, 3000)
All function calls should return a result, as if the request was actually made.
I think for this purpose I can use RxJS library.
I have done this:
const request$ = new Subject < string > ();
const response$ = request.pipe(
groupBy((url: string) => url),
flatMap(group => group.pipe(auditTime(500))), // make a request no more than once every 500 msec
map((url: string) => [
url,
from(fetch(url))
]),
share()
);
const doAJAX = (url: string): Observable <any> {
return new Observable(observe => {
response$
.pipe(
filter(result => result[0] === url),
first(),
flatMap(result => result[1])
)
.subscribe(
(response: any) => {
observe.next(response);
observe.complete();
},
err => {
observe.error(err);
}
);
request$.next(url);
});
}
I create request$ subject and response$ observable. doAjax function subscribes for response$ and send URL string to request$ subject. Also there are groupBy and auditTime operators in request$ stream. And filter operator in doAJAX function.
This code works but I think it is very difficult. Is there a way to make this task easier? Maybe RxJS scheduler or not use RxJS library at all
As the whole point of this is to memoize Http results and delay repeated calls, you might consider your own memoization. Example:
const memoise = (func) => {
let cache: { [key:string]: Observable<any> } = {};
return (...args): Observable<any> => {
const cacheKey = JSON.stringify(args)
cache[cacheKey] = cache[cacheKey] || func(...args).pipe(share());
return cache[cacheKey].pipe(
tap(() => timer(1000).subscribe(() => delete cache[cacheKey]))
);
}
}
Here is a Stackblitz DEMO
Since I update my code to the new Rxjs 6, I had to change the interceptor code like this:
auth.interceptor.ts:
...
return next.handle(req).pipe(
tap((event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
// do stuff with response if you want
}
}),
catchError((error: any) => {
if (error instanceof HttpErrorResponse) {
if (error.status === 401) {
this.authService.loginRedirect();
}
return observableThrowError(this.handleError(error));
}
})
);
and I'm not able to test the rxjs operators "tap" and "catchError".
Actually i'm only able to test if pipe is called:
it('should intercept and handle request', () => {
const req: any = {
clone: jasmine.createSpy('clone')
};
const next: any = {
handle: () => next,
pipe: () => next
};
spyOn(next, 'handle').and.callThrough();
spyOn(next, 'pipe').and.callThrough();
interceptor.intercept(req, next);
expect(next.handle).toHaveBeenCalled();
expect(next.pipe).toHaveBeenCalled();
expect(req.clone).toHaveBeenCalled();
});
Any help is apreciated on how to spy the rxjs operators
I think the problem is that you shouldn't be testing that operators were called like this at the first place.
Operators in both RxJS 5 and RxJS 6 are just functions that only "make recipe" how the chain is constructed. This means that checking if tap or catchError were called doesn't tell you anything about it's functionality or whether the chain works at all (it might throw an exception on any value and you won't be able to test it).
Since you're using RxJS 6 you should rather test the chain by sending values through. This is well documented and pretty easy to do https://github.com/ReactiveX/rxjs/blob/master/doc/marble-testing.md
In your case you could do something like this:
const testScheduler = new TestScheduler((actual, expected) => {
// some how assert the two objects are equal
// e.g. with chai `expect(actual).deep.equal(expected)`
});
// This test will actually run *synchronously*
testScheduler.run(({ cold }) => {
const next = {
handle: () => cold('-a-b-c--------|'),
};
const output = interceptor.intercept(null, next);
const expected = ' ----------c---|'; // or whatever your interceptor does
expectObservable(output).toBe(expected);
});
I think you'll get the point what this does...
I'm working with RxJs and I have to make a polling mechanism to retrieve updates from a server.
I need to make a request every second, parse the updates, emit it and remember its id, because I need it to request the next pack of updates like getUpdate(lastId + 1).
The first part is easy so I just use interval with mergeMap
let lastId = 0
const updates = Rx.Observable.interval(1000)
.map(() => lastId)
.mergeMap((offset) => getUpdates(offset + 1))
I'm collecting identifiers like this:
updates.pluck('update_id').scan(Math.max, 0).subscribe(val => lastId = val)
But this solution isn't pure reactive and I'm looking for the way to omit the usage of "global" variable.
How can I improve the code while still being able to return observable containing just updates to the caller?
UPD.
The server response for getUpdates(id) looks like this:
[
{ update_id: 1, payload: { ... } },
{ update_id: 3, payload: { ... } },
{ update_id: 2, payload: { ... } }
]
It may contain 0 to Infinity updates in any order
Something like this? Note that this is an infinite stream since there is no condition to abort; you didn't give one.
// Just returns the ID as the update_id.
const fakeResponse = id => {
return [{ update_id: id }];
};
// Fakes the actual HTTP call with a network delay.
const getUpdates = id => Rx.Observable.of(null).delay(250).map(() => fakeResponse(id));
// Start with update_id = 0, then recursively call with the last
// returned ID incremented by 1.
// The actual emissions on this stream will be the full server responses.
const updates$ = getUpdates(0)
.expand(response => Rx.Observable.of(null)
.delay(1000)
.switchMap(() => {
const highestId = Math.max(...response.map(update => update.update_id));
return getUpdates(highestId + 1);
})
)
updates$.take(5).subscribe(console.log);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.6/Rx.js"></script>
To define the termination of the stream, you probably want to hook into the switchMap at the end; use whatever property of response to conditionally return Observable.empty() instead of calling getUpdates again.