I'm trying to understand how Observables and RxJS works, so this might be not at all the point of how to use them.
I have an Angular2 application and am additionally using RxJS Observables to send events around. Now for a special type of error events, I'd like to know if the event has already been handled by another Subscriber. Multiple Subscribers might exist on the Observable and some might take full responsibility of the event so that others won't get it anymore.
The idea comes from how Routed Events work in WPF. In the event handler you get the RoutedEventArgs parameter, which has a Property Handled:
If setting, set to true if the event is to be marked handled; otherwise false. If reading this value, true indicates that either a class handler, or some instance handler along the route, has already marked this event handled. false.indicates that no such handler has marked the event handled.
Another implementation example would be how the middleware works in the ASP.NET Core Pipeline - https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware - You can either call the next middleware or just return a result.
I was thinking about adding the Handled property to the event I'd throw into the observable pipe, but I'm not sure if it's the idiomatic way of doing this in RxJS.
Typically the way you do this with observables is you don't hand the observable to everyone and everyone subscribes to it. Instead you give each interested party a chance to "add to the pipeline" and then finally subscribe once. There are many ways to do this. The easiest is to not actually give anyone the observable. But instead let them provide you with callbacks:
class Foo {
observable = ...;
callbacks = [];
addCallback(callback) { this.callbacks.push(callback); }
constructor() {
// subscribe to the observable and call any registered callbacks
this.observable.subscribe(e => {
for (const cb of this.callbacks) {
// callback returns true if it has "handled" the event
if (cb(e)) {
return; // do not call any other callbacks
}
}
});
}
}
const foo = new Foo();
// subscriber1 handles "type1" events
foo.addCallback(ev => ev.type === "type1");
// subscriber2
foo.addCallback(ev => ev.type === "type2");
This is the simplest way. There are other ways where Foo exposes observables for each client and monitors their results to build the pipeline.
Related
I'm learning RxJS and am rather confused as to where the "listeners" are (in the Observable or the Observer), how they are subscribed/unsubscribed, and what happens when an Observer is "no longer interested in" an Observable, such as when you use take or takeUntil.
For the first part -- what's subscribed to what, what's a listener -- I'm confused by the seeming contradiction between these statements. From http://reactivex.io/rxjs/manual/overview.html we read that Observers are not 'listeners' to Observables
This is drastically different to event handler APIs like
addEventListener / removeEventListener. With observable.subscribe, the
given Observer is not registered as a listener in the Observable. The
Observable does not even maintain a list of attached Observers.
but in http://reactivex.io/learnrx/ it says (Exercise 30) (highlighting mine) that
An Observable based on an Event will never complete on its own. The
take() function creates a new sequence that completes after a discrete
number of items arrive. This is important, because unlike an Event,
when an Observable sequence completes it unsubscribes all of its
listeners. That means that if we use take() to complete our Event
sequence, we don't need to unsubscribe!
This seems contradictory to me. When you set up an Observable with, for example, fromEvent, where is the event listener? When you use take(1), for instance, on an Observable based on DOM events, what happens after the first event is sent to the observer? Does the Observer unsubscribe from the Observable, which continues to emit events, it's just that the Observer isn't listening to them anymore? Or does the Observable somehow unsubscribe the Observer, that is, the eventListener was in the Observable, not the Observer?
Thanks for any clues -- obviously I'm not seeing the forest for the trees, but the tutorials I'm working through, while they are good at trying to explain it conceptually, leave me confused as to what's actually going on.
The first part is being rather particular about its use of words in order to highlight that subscribing to an observable is a matter of calling a function (or more likely a chain of functions) to run all the code they contain. The second part is less particular about its wording, but it's not really talking about the same thing. If you like, the second part would be better worded as "when an observable completes, it calls teardown logic on its observers.
Let me try to describe what i mean when i say that subscribing to an observable is a matter of calling a chain of functions. Consider the following super simple example:
For a super simple example, suppose i create this observable:
const justOne = Rx.Observable.create(function realSubscribe(observer) {
observer.next(1);
observer.complete();
});
justOne.subscribe(val => console.log(val));
If i then call justOne.subscribe(val => console.log(val)), doing so will immediately call the function i named realSubscribe. It then does observer.next(1), which results in logging out val, then it does observer.complete(). And that's it.
No where in this process did the observable create or augment a list of subscribers; it just ran through the code sequentially and then was done.
Now moving onto a slightly more realistic example, let's consider fromEvent. If i were to implement it, it might look something like this (the real implementation is more complicated, but this gets the gist of it):
function fromEvent(element, eventName) {
return Rx.Observable.create(function subscribeToEvent(observer) {
element.addEventListener(eventName, observer.next);
return function cleanup() {
element.removeEventListener(eventName, observer.next);
}
});
}
const observable = fromEvent(document, 'click');
const subscription = observable.subscribe(event => console.log(event));
Now when i call observable.subscribe, it runs subscribeToEvent, and in so doing it calls addEventListener on the document. document.addEventListener does result in the document keeping a list of event listeners, but that's because of the way addEventListener is implemented, not something common to all observables. The observable itself doesn't keep track of any listeners. It just calls what it's told to call, and then returns a cleanup function.
Next up let's look at take. As before the real implementation is more complicated, but here's roughly what it does:
// In the real `take`, you don't need to pass in another observable since that's
// available automatically from the context you called it in. But my sample code
// has to get it somehow.
function take(count, otherObservable) {
return new Observable(function subscribeToTake(observer) {
let soFar = 0;
otherObservable.subscribe((value) => {
observer.next(value);
soFar++;
if (soFar >= count) {
observer.complete();
}
});
});
}
const clickObservable = fromEvent(document, 'click');
take(1, clickObservable).subscribe(event => console.log(event))
As mentioned in the comment, the syntax i'm using doesn't quite match how it would be use in rxjs, but that's because to mimic that would require a more full implementation. Anyway, the main thing to draw your attention to is that we're starting to produce a chain of functions:
When i call .subscribe, that calls subscribeToTake. This sets up a counter, and then calls otherObservable.subscribe, which is subscribeToEvent. subscribeToEvent then calls document.addEventListener.
Take's job is to sit in the middle of this function chain. It keeps track of how many values have been emitted so far. If the count is low enough, it just forwards the values along. But once the count is reached, it will call complete, thus ending the observable. Calling complete causes the observable to run any teardown logic it has, or anything its chain has. There's no teardown logic for take, but fromEvent will run some teardown logic to remove the event listener.
I am very new to observables am worried about memory leaks. If I create the following:
private client = new BehaviorSubject("");
clientStream$ = this.client.asObservable();
and susbscirbe to them in views like so:
this.clientService.clientStream$.subscribe(
client => {
this.client = client;
}
}
do I need to unsubscribe? What if I called client.getValue()?
do I need to unsubscribe?
Probably.
If you're designing a subject which will complete -- ie, if you intend to callclient.complete() (or client.onCompleted() if you're using rxjs 4) -- then this will tear down the subscriptions automatically.
But often times, your behavior subject will be in some service which persists, and you don't want it to complete. In that case, you will need to unsubscribe. There are two ways you can unsubscribe:
1) Manually:
When you call .subscribe, you get back a subscription object. If you call .unsubscribe() on it (.dispose() in rxjs 4), you will unsubscribe. For example:
const subscription = this.clientService.clientStream$
.subscribe(client => this.client = client);
setTimeout(() => subscription.unsubscribe(), 10000); // unsubscribe after 10 seconds
2) Automatically, based on another observable. If you're using observables often in your application, you will probably find this approach to be very convenient.
Observables have a .takeUntil operator, which you can pass in another observable to. When that second observable emits a value, it will do the unsubscription for you. This lets you describe up front what conditions should tear down your observable. For example:
this.clientService.clientStream$
.takeUntil(Observable.timer(10000))
.subscribe(client => this.client = client);
What if I called client.getValue()
That will synchronously give you the current value. You're not subscribing at all. On the up side, this means you won't need to unsubscribe. But on the downside, why are you using a behavior subject if you're not interested in seeing when the value changes?
I have an observer that goes like this.
var source = rx.Observable.fromEvent(eventAppeared.emitter, 'event')
.filter(mAndF.isValidStreamType)
.map(mAndF.transformEvent)
.share();
I then share it with a number of subscribers. These subscribers all take the event and perform some async operations on them.
so my subscribers are like
source.subscribe(async function(x) {
const func = handler[x.eventName];
if (func) {
await eventWorkflow(x, handler.handlerName, func.bind(handler));
}
});
There's a bit of extra stuff in there but I think the intent is clear.
I need every "handler" that handles this particular event to handle it and block till it gets back. Then process the next event.
What I've found with the above code is that it's just calling the event with out awaiting it and my handlers are stepping on themselves.
I've read a fair number of posts, but I can't really see how to do it. Most people are talking about making the observer awaitable. But that's not what I need is it? It seems like what I need is to make the observer awaitable. I can't find anything on that which usually means it's either super easy or a super ridiculous thing to do. I'm hoping for the former.
Please let me know if you need any further clarification.
---update---
what I have realized is that what I need is a fifo queue or buffer ( first in first out ), sometimes referred to as back pressure. I need all messages processed in order and only when the preceding message is done processing.
---end update---
at first I thought it was cuz I was using rx 2.5.3 but I just upgraded to 4.1.0 and it's still not synchronous.
There's no way to tell a source observable to put events on hold from within a subscribe, it just lets us "observe" incoming events. Asynchronous things should be managed via Rx operators.
For example, to let your asynchronous handlers process events sequentially, you could try to use concatMap operator:
source
.concatMap(x => {
const func = handler[x.eventName];
return func ?
eventWorkflow(x, handler.handlerName, func.bind(handler)) :
Rx.Observable.empty();
})
.subscribe();
Note that in the example above await is not needed as concatMap knows how to deal with a promise which eventWorkflow returns: concatMap converts it to an observable and waits until the observable completes before proceeding with the next event.
So ultimately what I have found is that what I need is more accurately described as a fifo queue or buffer. I need the messages to wait until the previous message is done processing.
I have also pretty certain that rxjs doesn't offer this ( sometimes referred to as backpressure ). So what I have done is to just import a fifo queue and hook it to each subscriber.
I am using concurrent-queue which so far seems to be working pretty well.
Let's say we have an Observable:
var observable = Rx.Observable
.fromEvent(document.getElementById('emitter'), 'click');
How can I make it Complete (what will trigger onComplete event for all subscribed Observers) ?
In this present form, you cannot. Your observable is derived from a source which does not complete so it cannot itself complete. What you can do is extend this source with a completing condition. This would work like :
var end$ = new Rx.Subject();
var observable = Rx.Observable
.fromEvent(document.getElementById('emitter'), 'click')
.takeUntil(end$);
When you want to end observable, you do end$.onNext("anything you want here");. That is in the case the ending event is generated by you. If this is another source generating that event (keypress, etc.) then you can directly put an observable derived from that source as an argument of takeUntil.
Documentation:
http://reactivex.io/documentation/operators/takeuntil.html
https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/takeuntil.md
What worked for me is using the take() operator. It will fire the complete callback after x number of events. So by passing 1, it will complete after the first event.
Typescript:
private preloadImage(url: string): Observable<Event> {
let img = new Image();
let imageSource = Observable.fromEvent(img, "load");
img.src = url;
return imageSource.take(1);
}
I think what you are looking for is the dispose() method.
from: https://github.com/Reactive-Extensions/RxJS/blob/master/doc/gettingstarted/creating.md#cold-vs-hot-observables
Notice that the subscribe method returns a Disposable, so that you can unsubscribe to a sequence and dispose of it easily. When you invoke the dispose method on the observable sequence, the observer will stop listening to the observable for data. Normally, you do not need to explicitly call dispose unless you need to unsubscribe early, or when the source observable sequence has a longer life span than the observer. Subscriptions in Rx are designed for fire-and-forget scenarios without the usage of a finalizer. Note that the default behavior of the Observable operators is to dispose of the subscription as soon as possible (i.e, when an onCompleted or onError messages is published). For example, the code will subscribe x to both sequences a and b. If a throws an error, x will immediately be unsubscribed from b.
I found an easier way to do this for my use case, If you want to do something when the observable is complete then you can use this:
const subscription$ = interval(1000).pipe(
finalize(() => console.log("Do Something")),
).subscribe();
The finalize is triggered on complete, when all subscriptions are unsubscribed etc.
I have an $http promise in an angular app like this:
this.data = $http.get('/api/foo', {})
Other parts of my app then add success and error handlers to this promise.
My problem is that I want to refresh the information within the this.data variable and then re-run all the promise's attached handlers. Can this be done with some sort of this.data.$refresh() method, or would I have to store all the handlers somewhere else and reattach them to a new $http.get?
EDIT: Maybe a slightly clearer example:
this.data = $http.get('/api/foo', {})
this.data.success(doSomething)
// doSomething() runs because the response arrives.
this.data.someMagic()
// doSomething() runs again without being reattached.
What I want to avoid is this:
this.data = $http.get('/api/foo', {})
this.data.success(doSomething)
// Time passes...
this.data = $http.get('/api/foo', {}) // All old handlers have now been thrown away.
this.data.success(doSomething)
This is because there are several handlers on both success and error, and they are added by different controllers and services, so it would require some messy callback system to get them all to reattach their handlers every time the variable was updated.
re-run all the promise's attached handlers.
No, that's impossible. By contract, a promise resolves and executes its handlers only once.
would I have to store all the handlers somewhere else and reattach them to a new $http.get?
Yes, that's a possible solution, although it looses all the nice properties of promises like chainability. You might as well simply put an EventEmitter and implement some kind of pub-sub (see Angularjs pubsub vs $broadcast for example).
If you want to have a real stream interface with all kinds of goodies, you may want to look into FRP, e.g. with Bacon.js and angular-bacon.