RxJs: How to know which operators closes stream? - rxjs

I am interested, is there a way to know whether the operator closes a stream or not?
I've been trying to find it in documentation, but with no luck today.

I think you are looking for "complete" callback (or third parameter) of subscribe() method [see the detailing in the comments] -
yourObservable.pipe(
take(1) //or take any number of values i.e. which is a finite number,
map(),
//or some other operators as per your requirement
).subscibe(
//first call back is to handle the emitted value
//this will be called every time a new value is emitted by observable
(value) => {
//do whatever you want to do with value
console.log(value);
},
//second callback is for handling error
//This will be called if an observable throws an exception
//once an exception occurred then also observable complete and no more values recieved by the subscriber
//Either complete callback is called or error callback is called but not
//both
(exception) => {
//do whatever you want to do with error
console.log(error);
},
//third callback will be called only when source observable is complete
//otherwise it will never get called
//This is the place to know if an observable is completed or not
//Once complete callback fires, subscription automatically unsubscribed
() => {
console.log(`Observable is complete and will not emit any new value`)
}
);
see the following stackblitz - https://stackblitz.com/edit/rxjs-toarray-xwvtgk?file=index.ts&devtoolsheight=100

Related

How can I wait for multiple subscriptions?

I'm trying to make an API call for each element of an array, and emit an specific event if every API response is true.
I was doing the following:
let emit = true
array.forEach(element => {
this.service.getElement(element).subscribe(loaded => {
if(!loaded){
emit = false;
}
});
});
this.loaded.emit(emit);
But the last line always emits true
How can I wait for every request to be resolved before making the output event emission?
So the last line will always emit true because the service code is executed asynchronously. This means that it executes everything except the callback function in the subscribe method. This callback function executed once the stream emits a new element. I guess, you are making Http requests to an endpoint here. If so, the callback function is executed for each Http response and the order is not guaranteed.
What you can do is:
forkJoin(
...array.map(e => this.service.getElement(e))
).subscribe(responseArray =>
console.log(responseArray);
console.log('Complete');
);
The callback only executes if all Http response are available. And responseArray has the exact same order as the elements in the array.
Note: Keep in mind that if you do
let emit = false;
forkJoin(...).subscribe(() => emit = true);
console.log(emit);
It would print you false, because the callback executes async. If that seems like strange behaviour for you, I'd highly recommend to read about the JavaScript Eventloop.
Cause call http API is async, so this.loaded.emit(emit) executed first;
fix:
let emit = true
array.forEach(element => {
this.service.getElement(element).subscribe(loaded => {
if(!loaded){
emit = false;
this.loaded.emit(emit);
}
});
});
If you want execute this.loaded.emit(emit) when all of API response is true, try use forkJoin.

subjectify methods with callbacks

I often use the promisify method, which converts a method with a callback signature (e.g. fucntion fn(cb) { ... }) to a method which returns a Promise. It can make the source code a lot cleaner and more compact. So far so good.
Slightly different, are methods which have a callback method, but the callback is called multiple times. In that case a Promise cannot do the trick, because a promise can only be executed once.
In theory, those methods could return a Subject. (e.g. a BehaviorSubject) which would then be fired multiple times.
This made me wondering:
Is there a subjectify method somewhere, which can do this for me ?
For example: it could be useful when there is a method which parses a big document. To report on its progress, it could use a callback method. But wrapping it in a Subject could be more convenient.
There are some operator that helps you convert callback to observable
https://rxjs-dev.firebaseapp.com/api/index/function/fromEventPattern
but you can quite easily integrate subject to callback like below
const progress=new Subject()
myEvent.on('progress', (p)=>{
progress.next(p)
})
You may want to consider to create an Observable in cases your callback is called repeatedly and you want to notify a stream of events as a result of the callback being called.
Let's consider, as an example, the node readline function, which accepts a callback which gets fired for each line read and a second callback which is called when the file end is reached.
In this case, we can create an Observable which emits for each line read and completes when the end is reached, like in the following example
function readLineObs(filePath: string) => {
return new Observable(
(observer: Observer<string>): TeardownLogic => {
const rl = readline.createInterface({
input: fs.createReadStream(filePath),
crlfDelay: Infinity,
});
rl.on('line', (line: string) => {
observer.next(line);
});
rl.on('close', () => {
observer.complete();
});
},
);
};

Is it safe to assume that all nested RxJS Subscribe can be replaced by mergeMap?

I got to refactor some code for a tremendous angular project I'm newly involved in. Some parts of the code lake of RxJS operators and do things like simple nested subscribes or copy/paste error handlers instead of using pipes.
Is it safe to assume that any simple 1 depth nested subscribes can be replaced by a mergeMap?
Let's take a login method like this :
private login() {
this.userService.logIn(this.param1, this.param2).subscribe((loginResult: {}) => {
this.userService.getInfo(this.param3).subscribe((user: UserModel) => {
// [the login logic]
},
(e) => {
// [the error handling logic]
})
}, (e) => {
// [The exact same copy/pasted error handling logic]
});
}
Is it safe to replace it with this?
private login() {
this.userService.logIn(this.param1, this.param2)
.pipe(
mergeMap((x) => this.userService.getInfo(this.param3))
)
.subscribe((user: UserModel) => {
// [the login logic that redirects to "my account" page]
},
(e) => {
// [the error handling logic]
});
}
What would be the difference with flatMap or switchMap here, for instance?
For your case I will go with SwitchMap.
mergeMap/FlatMap: transforms each emitted item to a new observable as defined by a function. Executes the executions in parallel and merges the results (aka flatMap) order doesn't matter.
swicthMap: transforms each emitted item to a new observable as defined by a function.
-Subscribers from prior inner observable.
-Subscribers to new inner observable.
-Inner observable are merged to the output stream.
-When you submit a new observable, multiple times the previous one gets cancelled and only emits the last one which is awesome for performance.

'fromEvent' always observing, but 'from(myArray)' terminates observing after all values are processed

So Im struggling a bit with my understanding of RxJs and observables.
Can it be explained why 'fromEvent' always reacts to a new event no matter how much time has passed without a new value...yet pushing new values into an existing array that is being observed using 'from' doesn't work in the same way if they are both observables at this point and should react to async events why do we need to use 'Subject' for arrays?
I have seen we need to use a 'Subject' for an array ...but why? I would like to understand the reason/mechanism
This is a little bit like asking why and Array.forEach doesn't also iterate over items I add to an array via .push while addEventListener() continues listening for events even far in the future.
The behaviors of the two are different because the underlying data structures are different.
#fromArray
For an Array the implementation is essentially:
function fromArray(array) {
return new Observable(observer => {
try {
// Iterate through each item in the array and emit it to the Observer
array.forEach(item => observer.next(item));
// Array iteration is synchronous, which when we get here we are done iterating
observer.complete();
} catch (e) { observer.error(e) }
})
}
Where the function passed to the observable gets run each time a subscriber subscribes to the Observable. Because Arrays don't have a mechanism to detect changes to them there is no way to listen for additional updates to a native array (note: I am ignoring monkey-patching or creating some sort of substitute array data type that does support such things for simplicity).
#fromEvent
The fromEvent on the other hand would look more like:
function fromEvent(node, eventName, selector) {
// Convert the passed in event or just use the identity
let transform = selector || x => x;
// Construct an Observable using the constructor
return new Observable(observer => {
// Build a compatible handler, we also use this for the unsubscribe logic
const nextHandler = (value) => {
try {
observer.next(transform(value));
} catch (e) { observer.error(e); }
}
// Start listening for events
node.addEventListener(eventName, nextHandler);
// Return a way to tear down the subscription when we are done
return () => node.removeEventListener(eventName, nextHandler);
})
// Shares the underlying Subscription across multiple subscribers
// so we don't create new event handlers for each.
.share();
}
Here we are simply wrapping the native event handler (obviously the real implementation is more robust than this). But because the underlying source is actually an event handler which does have a mechanism to report new event (by definition really), we continue to get events in perpetuity (or until we unsubscribe).

RxJS: Auto (dis)connect on (un)subscribe with Websockets and Stomp

I'm building a litte RxJS Wrapper for Stomp over Websockets, which already works.
But now I had the idea of a really cool feature, that may (hopefully - correct me if I'm wrong) be easily done using RxJS.
Current behavior:
myStompWrapper.configure("/stomp_endpoint");
myStompWrapper.connect(); // onSuccess: set state to CONNECTED
// state (Observable) can be DISCONNECTED or CONNECTED
var subscription = myStompWrapper.getState()
.filter(state => state == "CONNECTED")
.flatMap(myStompWrapper.subscribeDestination("/foo"))
.subscribe(msg => console.log(msg));
// ... and some time later:
subscription.unsubscribe(); // calls 'unsubscribe' for this stomp destination
myStompWrapper.disconnect(); // disconnects the stomp websocket connection
As you can see, I must wait for state == "CONNECTED" in order to subscribe to subscribeDestination(..). Else I'd get an Error from the Stomp Library.
The new behavior:
The next implementation should make things easier for the user. Here's what I imagine:
myStompWrapper.configure("/stomp_endpoint");
var subscription = myStompWrapper.subscribeDestination("/foo")
.subscribe(msg => console.log(msg));
// ... and some time later:
subscription.unsubscribe();
How it should work internally:
configure can only be called while DISCONNECTED
when subscribeDestination is called, there are 2 possibilities:
if CONNECTED: just subscribe to the destination
if DISCONNECTED: first call connect(), then subscribe to the destination
when unsubscribe is called, there are 2 possibilities:
if this was the last subscription: call disconnect()
if this wasn't the last subscription: do nothing
I'm not yet sure how to get there, but that's why I ask this question here ;-)
Thanks in advance!
EDIT: more code, examples and explanations
When configure() is called while not disconnected it should throw an Error. But that's not a big deal.
stompClient.connect(..) is non-blocking. It has an onSuccess callback:
public connect() {
stompClient.connect({}, this.onSuccess, this.errorHandler);
}
public onSuccess = () => {
this.state.next(State.CONNECTED);
}
observeDestination(..) subscribes to a Stomp Message Channel (= destination) and returns an Rx.Observable which then can be used to unsubscribe from this Stomp Message Channel:
public observeDestination(destination: string) {
return this.state
.filter(state => state == State.CONNECTED)
.flatMap(_ => Rx.Observable.create(observer => {
let stompSubscription = this.client.subscribe(
destination,
message => observer.next(message),
{}
);
return () => {
stompSubscription.unsubscribe();
}
}));
}
It can be used like this:
myStompWrapper.configure("/stomp_endpoint");
myStompWrapper.connect();
myStompWrapper.observeDestination("/foo")
.subscribe(..);
myStompWrapper.observeDestination("/bar")
.subscribe(..);
Now I'd like to get rid of myStompWrapper.connect(). The code should automatically call this.connect() when the first one subscribes by calling observeDestination(..).subscribe(..) and it should call this.disconnect() when the last one called unsubscribe().
Example:
myStompWrapper.configure("/stomp_endpoint");
let subscription1 = myStompWrapper.observeDestination("/foo")
.subscribe(..); // execute connect(), because this
// is the first subscription
let subscription2 = myStompWrapper.observeDestination("/bar")
.subscribe(..);
subscription2.unsubscribe();
subscription1.unsubscribe(); // execute disconnect(), because this
// was the last subscription
RxJS: Auto (dis)connect on (un)subscribe with Websockets and Stomp
I agree the code you are suggesting to tuck away into myStompWrapper will be happier in its new home.
I would still suggest to use a name like observeDestination rather than subscribeDestination("/foo") as you are not actually subscribing from that method but rather just completing your observable chain.
configure() can only be called while DISCONNECTED
You do not specify here what should happen if it is called while not DISCONNECTED. As you do not seem to be returning any value here that you would use, I will assume that you intend to throw an exception if it has an inconvenient status. To keep track of such statuses, I would use a BehaviourSubject that starts with the initial value of DISCONNECTED. You likely will want to keep state within observeDestination to decide whether to throw an exception though
if CONNECTED: just subscribe to the destination
if DISCONNECTED: first call connect(), then subscribe to the destination
As I mentioned before, I think you will be happier if the subscription does not happen within subscribeDestination("/foo") but rather that you just build your observable chain. As you simply want to call connect() in some cases, I would simply use a .do() call within your observable chain that contains a condition on the state.
To make use of the rx-y logic, you likely want to call disconnect() as part of your observable unsubscribe and simply return a shared refcounted observable to start with. This way, each new subscriber does not recreate a new subscription, instead .refCount() will make a single subscription to the observable chain and unsubscribe() once there is no more subscribers downstream.
Assuming the messages are coming in as this.observedData$ in myStompWrapper My suggested code as part of myStompWrapper would look something like this:
observeDestination() {
return Rx.Observable.create(function (observer) {
var subscription = this.getState()
.filter(state => state == "CONNECTED")
.do(state => state ? this.connect() : Observable.of(true))
.switchMap(this.observedData$)
.refCount();
.subscribe(value => {
try {
subscriber.next(someCallback(value));
} catch(err) {
subscriber.error(err);
}
},
err => subscriber.error(err),
() => subscriber.complete());
return { unsubscribe() { this.disconnect(); subscription.unsubscribe(); } };
}
Because I am missing some of your code, I am allowing myself to not test my code. But hopefully it illustrates and presents the concepts I mentioned in my answer.

Resources