RxJS5 WebSocketSubject - how to filter and complete messages? - websocket

I'm looking for some guidance on the correct way to setup a WebSocket connection with RxJS 5. I am connecting to a WebSocket that uses JSON-RPC 2.0. I want to be able to execute a function which sends a request to the WS and returns an Observable of the associated response from the server.
I set up my initial WebSocketSubject like so:
const ws = Rx.Observable.webSocket("<URL>")
From this observable, I have been able to send requests using ws.next(myRequest), and I have been able to see responses coming back through the ws` observable.
I have struggled with creating functions that will filter the ws responses to the correct response and then complete. These seem to complete the source subject, stopping all future ws requests.
My intended output is something like:
function makeRequest(msg) {
// 1. send the message
// 2. return an Observable of the response from the message, and complete
}
I tried the following:
function makeRequest(msg) {
const id = msg.id;
ws.next(msg);
return ws
.filter(f => f.id === id)
.take(1);
}
When I do that however, only the first request will work. Subsequent requests won't work, I believe because I am completing with take(1)?
Any thoughts on the appropriate architecture for this type of situation?

There appears to be either a bug or a deliberate design decision to close the WebSocket on unsubscribe if there are no further subscribers. If you are interested here is the relevant source.
Essentially you need to guarantee that there is always a subscriber otherwise the WebSocket will be closed down. You can do this in two ways.
Route A is the more semantic way, essentially you create a published version of the Observable part of the Subject which you have more fine grained control over.
const ws = Rx.Observable.webSocket("<URL>");
const ws$ = ws.publish();
//When ready to start receiving messages
const totem = ws$.connect();
function makeRequest(msg) {
const { id } = msg;
ws.next(msg);
return ws$.first(f => f.id === id)
}
//When finished
totem.unsubscribe();
Route B is to create a token subscription that simply holds the socket, but depending on the actual life cycle of your application you would do well to attach to some sort of closing event just to make sure it always gets closed down. i.e.
const ws = Rx.Observable.webSocket("<URL>");
const totem = ws.subscribe();
//Later when closing:
totem.unsubscribe();
As you can see both approaches are fairly similar, since they both create a subscription. B's primary disadvantage is that you create an empty subscription which will get pumped all the events only to throw them away. They only advantage of B is that you can refer to the Subject for emission and subscription using the same variable whereas A you must be careful that you are using ws$ for subscription.
If you were really so inclined you could refine Route A using the Subject creation function:
const safeWS = Rx.Subject.create(ws, ws$);
The above would allow you to use the same variable, but you would still be responsible for shutting down ws$ and transitively, the WebSocket, when you are done with it.

Related

How can I use `firstValueFrom` with `WebSocketSubject` without closing the underlying web socket?

I am using a WebSocketSubject, and I often want to block execution until a given event arrives, which is why I use firstValueFrom, like in the following code:
let websocket = new WebSocketSubject<any>(url);
let firstMessage = await firstValueFrom(websocket.pipe(filter(m => true));
I only have one issue, which is that firstValueFrom calls websocket.unsubscribe() when it resolves the promise, but on a WebSocketSubject that has the effect of closing the underlying Web Socket, which I want to keep open!
Currently, I have thought of a few possible ways out:
Writing an equivalent of firstValueFrom that does not unsubscribe.
Counter argument: I would prefer not reimplementing a function that is nearly perfect, except for one small issue;
Using another Subject that will subscribe to WebSocketSubject, and I will use firstValueFrom on that subject.
Counter argument: In terms of usage, I see potential confusion to have two Subject objects, and having to know which one to use (E.g. Use websocket.next for sending messages upstream, and only use websocketProxy for receiving messages, and never get confused between the two!);
Using multiplex to create temporary Observable objects that will then be closed by firstValueFrom without issue.
Counter argument: As I am not actually multiplexing in this case, I would rather not use that method, whose signature and usage seems overkill for my use case.
In short, I suspect that I am missing something basic (e.g. an appropriate OperatorFunction) that would allow me to make it so that the unsubscribe call made by firstValueFrom does not result in the underlying web socket being closed.
Essentially, you want to always have a subscription so the socket connection stays open. I don't think firstValueFrom is the proper tool for the job. I think its simpler to just create an explicit subscription.
If the intent is to keep it open for the lifetime of the app, just subscribe at app launch.
Since you want to filter out the first several emissions until some condition is met, you can use skipWhile:
const websocket = new WebSocketSubject<any>(url);
const messages = websocket.pipe(skipWhile(m => m !== 'my special event'));
websocket.subscribe(); // keep socket open
// listen
messages.subscribe(m => console.log('message received:', m);
// send
websocket.next('hello server');
It may be worth creating a light wrapper class around the rxjs websocket that handles keeping the connection open and filtering out the first few events:
class MyWebsocket {
private websocket = new WebSocketSubject<any>(this.url);
public messages = websocket.pipe(skipWhile(m => m !== 'my special event'));
constructor(private url) {
this.websocket.subscribe(); // keep socket open
}
public sendMessage(message: any) {
this.websocket.sendMessage(message);
}
}
const websocket = new MyWebsocket(url);
// listen
websocket.messages.subscribe(m => console.log('message received:', m);
// send
websocket.sendMessage('hello server');

Queuing function using RxJS

I'm using rxjs with NodeJS in backend.
I have a Rest API which allow consumers to run remote yarn installation process. The install function returns an observable of the process. So when the module is installed successfully it emits a value in the observable and complete. At this point, the Rest API will returns a response to the user to say that the installation is successful. In case that the installation fails, the process will throw an Error in the stream and the Rest API returns another response with the error information.
My issue is:
The API is called multiple times in parallel by consumers, so there will be a parallel installations in the backend.
I tried throttle operator to create a queue but it keeps the first stream active. So if the first process is "completed", it returns "true" but the stream doesn't complete
export class MyService {
// the function called by the REST API
installGlobal(moduleName: string): Observable < boolean > {
// I think, there are something to do here to make it queuing
return this.run('yarn', ['global', 'add', moduleName]);
}
private run(cmd: string, args: string[]): Observable < boolean > {
const cmd$ = fromPromise(spawn(cmd, args)).pipe(
map(stdout => {
this.logger.info(`Install Module Successfully`);
this.logger.info(`stdout: ${stdout.toString()}`);
return true;
}),
catchError(error => {
const errorMessage: string = error.stderr.toString();
return _throw(errorMessage.substr(errorMessage.indexOf(' ') + 1));
})
);
return cmd$;
}
}
My expectation:
Either there are multiple request, they must be queued. So the first one will be treated and all parallel onces must be queued. When the first is processed, it must returns the response to the API consumers (like 200 completed) and resume the next stream from the queue.
[UPDATE-01 July 2019]: adding an example
You can fetch a demo of the code at stackblitz
I have reimplemented the existant code and i'm simulating my API call by subscribing multi time to the service which will call the queue
A simple queque in Rxjs can be done like below
const queque=new Subject()
// sequential processing
queue.pipe(concatMap(item=>yourObservableFunction(item)).subscribe()
// add stuff to the queue
queque.next(item)

deferred Rxjs BehaviorSubject mechanism

I have the following requirement.
I have An Angular service with an BehaviorSubject.
A http request is done and when this is done the BehaviorSubject.next method is invoked with the value.
This value can change during the lifecycle of the single page.
Different subscribers are registered to it and get invoked whenever this changes.
The problem is that while the http request is pending the BehaviorSubject already contains a default value and subscribers are already immediately getting this value.
What I would want is that subscribers have to wait till the http request is done (deferred) and get the value when the http request is done and sets the value.
So what I need is some kind of deferred Behavior subject mechanism.
How would i implement this using rxjs?
Another requirement is that if I subscribe to the behaviorsubject in a method we want the subcriber to get the first non default value and that the subscription ends. We don't want local subscriptions in functions to be re-executed.
Use a filter on your behavior subject so your subscribers won't get the first default emitted value:
mySubject$: BehaviorSubject<any> = new BehaviorSubject<any>(null);
httpResponse$: Observable<any> = this.mySubject$.pipe(
filter(response => response)
map(response => {
const modifyResponse = response;
// modify response
return modifyResponse;
}),
take(1)
);
this.httpResponse$.subscribe(response => console.log(response));
this.myHttpCall().subscribe(response => this.mySubject$.next(response));
You can of course wrap the httpResponse$ observable in a method if you need to.
I think the fact that you want to defer the emitted default value, straight away brings into question why you want to use a BehaviorSubject. Let's remember: the primary reason to use a BehaviorSubject (instead of a Subject, or a plain Observable), is to emit a value immediately to any subscriber.
If you need an Observable type where you need control of the producer (via .next([value])) and/or you want multicasting of subscription out of the box, then Subject is appropriate.
If an additional requirement on top of this is that subscribers need a value immediately then you need to consider BehaviorSubject.
If you didn't say you need to update the value from other non-http events/sources, then I would've suggested using a shareReplay(1) pattern. Nevertheless...
private cookieData$: Subject<RelevantDataType> = new
Subject<RelevantDataType>(null);
// Function for triggering http request to update
// internal Subject.
// Consumers of the Subject can potentially invoke this
// themselves if they receive 'null' or no value on subscribe to subject
public loadCookieData(): Observable<RelevantDataType> {
this.http.get('http://someurl.com/api/endpoint')
.map(mapDataToRelevantDataType());
}
// Function for dealing with updating the service's
// internal cookieData$ Subject from external
// consumer which need to update this value
// via non-http events
public setCookieData(data: any): void {
const newCookieValue = this.mapToRelevantDataType(data); // <-- If necessary
this.cookieData$.next(newCookieValue); // <-- updates val for all subscribers
}
get cookieData(): Observable<RelevantDataType> {
return this.cookieData$.asObservable();
}
The solution is based on OPs comments etc.
- deals with subscribing to subject type.
- deals with external subscribers not being able to 'next' a new value directly
- deals with external producers being able to set a new value on the Subject type
- deals with not giving a default value whilst http request is pending

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.

Time-based cache for REST client using RxJs 5 in Angular2

I'm new to ReactiveX/RxJs and I'm wondering if my use-case is feasible smoothly with RxJs, preferably with a combination of built-in operators. Here's what I want to achieve:
I have an Angular2 application that communicates with a REST API. Different parts of the application need to access the same information at different times. To avoid hammering the servers by firing the same request over and over, I'd like to add client-side caching. The caching should happen in a service layer, where the network calls are actually made. This service layer then just hands out Observables. The caching must be transparent to the rest of the application: it should only be aware of Observables, not the caching.
So initially, a particular piece of information from the REST API should be retrieved only once per, let's say, 60 seconds, even if there's a dozen components requesting this information from the service within those 60 seconds. Each subscriber must be given the (single) last value from the Observable upon subscription.
Currently, I managed to achieve exactly that with an approach like this:
public getInformation(): Observable<Information> {
if (!this.information) {
this.information = this.restService.get('/information/')
.cache(1, 60000);
}
return this.information;
}
In this example, restService.get(...) performs the actual network call and returns an Observable, much like Angular's http Service.
The problem with this approach is refreshing the cache: While it makes sure the network call is executed exactly once, and that the cached value will no longer be pushed to new subscribers after 60 seconds, it doesn't re-execute the initial request after the cache expires. So subscriptions that occur after the 60sec cache will not be given any value from the Observable.
Would it be possible to re-execute the initial request if a new subscription happens after the cache timed out, and to re-cache the new value for 60sec again?
As a bonus: it would be even cooler if existing subscriptions (e.g. those who initiated the first network call) would get the refreshed value whose fetching had been initiated by the newer subscription, so that once the information is refreshed, it is immediately passed through the whole Observable-aware application.
I figured out a solution to achieve exactly what I was looking for. It might go against ReactiveX nomenclature and best practices, but technically, it does exactly what I want it to. That being said, if someone still finds a way to achieve the same with just built-in operators, I'll be happy to accept a better answer.
So basically since I need a way to re-trigger the network call upon subscription (no polling, no timer), I looked at how the ReplaySubject is implemented and even used it as my base class. I then created a callback-based class RefreshingReplaySubject (naming improvements welcome!). Here it is:
export class RefreshingReplaySubject<T> extends ReplaySubject<T> {
private providerCallback: () => Observable<T>;
private lastProviderTrigger: number;
private windowTime;
constructor(providerCallback: () => Observable<T>, windowTime?: number) {
// Cache exactly 1 item forever in the ReplaySubject
super(1);
this.windowTime = windowTime || 60000;
this.lastProviderTrigger = 0;
this.providerCallback = providerCallback;
}
protected _subscribe(subscriber: Subscriber<T>): Subscription {
// Hook into the subscribe method to trigger refreshing
this._triggerProviderIfRequired();
return super._subscribe(subscriber);
}
protected _triggerProviderIfRequired() {
let now = this._getNow();
if ((now - this.lastProviderTrigger) > this.windowTime) {
// Data considered stale, provider triggering required...
this.lastProviderTrigger = now;
this.providerCallback().first().subscribe((t: T) => this.next(t));
}
}
}
And here is the resulting usage:
public getInformation(): Observable<Information> {
if (!this.information) {
this.information = new RefreshingReplaySubject(
() => this.restService.get('/information/'),
60000
);
}
return this.information;
}
To implement this, you will need to create your own observable with custom logic on subscribtion:
function createTimedCache(doRequest, expireTime) {
let lastCallTime = 0;
let lastResult = null;
const result$ = new Rx.Subject();
return Rx.Observable.create(observer => {
const time = Date.now();
if (time - lastCallTime < expireTime) {
return (lastResult
// when result already received
? result$.startWith(lastResult)
// still waiting for result
: result$
).subscribe(observer);
}
const disposable = result$.subscribe(observer);
lastCallTime = time;
lastResult = null;
doRequest()
.do(result => {
lastResult = result;
})
.subscribe(v => result$.next(v), e => result$.error(e));
return disposable;
});
}
and resulting usage would be following:
this.information = createTimedCache(
() => this.restService.get('/information/'),
60000
);
usage example: https://jsbin.com/hutikesoqa/edit?js,console

Resources