Angular 9 and rxjs - wait for message event after postMessage - rxjs

I am new to rxjs and not sure how to implement the follow logic. Any suggestion will be appreciated.
Background
I am going to implement the communication between host website and an iframe in it with postMessage. Since postMessage is one-way only, I would like to implement the logic to wait for 'response' by myself when a message is sent from host website to iframe.
I have a sync function called send(message) to invoke the postMessage to send message to iframe. Then I would like to have another function with the follow logic.
public async sendAndWait(message): Promise<responseObj> {
// 1. create an observable to wait to message event with timeout
// my first thought is as follow but I feel like it does not work
// fromEvent(window, 'message')
// .pipe(timeout(timeoutInMs))
// .subscribe(event => {
// console.info(event);
// });
// 2. run `send(message)` function
// 3. do not finish this function until timeout or receive event in the previous subscription.
}
When I use the function, I would like to have
let response = await sendAndWait(message);
Not sure if it is possible to implement? Thank you

You cannot stop code execution in JS (using Async-Await, a Promise object is returned behind the scenes. so that the code is never waiting)
Consider implementing it in the following way:
let response: responseObj;
function main(): void {
sendAndWait(MESSAGE_OBJECT).subscribe(x => response = x)
}
function sendAndWait(message): Observable<responseObj> {
send(message)
return fromEvent(window, 'message')
.pipe(
timeout(timeoutInMs),
first()
)
}
Or optionally returning Promise:
async function sendAndWait(message): Promise<void> {
send(message)
const response = await fromEvent(window, 'message')
.pipe(
timeout(timeoutInMs),
first(),
toPromise()
)
}

Related

RxJS Is Observer and Subscriber the same thing?

From this documentation:
RxJS introduces Observables, a new Push system for JavaScript. An Observable is a Producer of multiple values, "pushing" them to Observers (Consumers).
Subscribing to an Observable is analogous to calling a Function.
To invoke an Observable we should call the subscribe() function from the Observable object itself and pass the observer as the consumer of the data delivered by the observable like:
observable.subscribe( { /*this is an observer*/ } );
Also this documentation says:
What is an Observer? An Observer is a consumer of values delivered by an Observable. Observers are simply a set of callbacks, one for each type of notification delivered by the Observable: next, error, and complete. The following is an example of a typical Observer object:
On the other hand the first documentation says:
The Observable constructor takes one argument: the subscribe function.
The following example creates an Observable to emit the string 'hi' every second to a subscriber.
import { Observable } from 'rxjs';
const observable = new Observable(function subscribe(subscriber) {
const id = setInterval(() => {
subscriber.next('hi')
}, 1000);
});
When calling observable.subscribe with an Observer, the function subscribe in new Observable(function subscribe(subscriber) {...}) is run for that given subscriber. Each call to observable.subscribe triggers its own independent setup for that given subscriber.
So the entity Subscriber is just the argument passed into the subscribe function when creating a new Observable? If not who is the subscriber?
Are Observers and Subscribers the same entity? as mentioned in this documentation
Why isn't the code that invokes observable.subscribe({observer as call backs}) the subscriber of the observable? Like the consumer of a function's return value is the code that makes the function call.
COMPLETE DESIGN PATTERN EXPLANATION
Observer
const observer = {
next: v => /* code for next callback*/,
error: err => /* code for error callback*/,
complete: () => /* code for completion callback*/
}
Subscription
const subscription = {
unsubscribe: () => /* code for unsubscribe callback */
}
Observable
const observable1 = from([1,2,3,4,5]);
const observable2 = of(1,2,3,4,5);
const observable3 = new Observable(observer => {
observer.next(1);
observer.next(2);
observer.next(3);
observer.next(4);
observer.next(5);
observer.complete();
return { // return a subscription
unsubscribe: () => /* code for unsubscribe callback */
};
});
Subscribe and use the Returned Subscription
// Store a subscription
const subscription = observable3.subscribe(observer);
// Invoke the unsubscribe callback defined by the observable.
subscription.unsubscribe();
Okay. Then What is a Subscriber?
[Subscriber] Implements the Observer interface and extends the Subscription class. While the Observer is the public API for consuming the values of an Observable, all Observers get converted to a Subscriber... Subscriber is a common type in RxJS, and crucial for implementing operators, but it is rarely used as a public API.
Are observers and subscribers the same thing? Kind of, yes? Depends on how concretely you ask the question.
Consider this:
observable3.subscribe({
next: v => /* code for next callback */
});
obsevable3.subscribe(
v => /* code for next callback */
);
The first is an object with only one observer property defined. The second is simply a lambda function. They both end up generating basically the same subscriber.

RxJS waiting for response before sending next command over UDP

I am currently working on a project where I send UDP commands to a Tello drone.
The problem is that it uses UDP and when I send commands too fast before the previous one hasn't finished yet, the second command/action doesn't take place. I am using RxJS for this project and I want to create a mechanism to wait for the response ("ok" or "error") from the drone.
My Idea is to have 2 different observables. 1 observable that is the input stream from the responses from the drone and one observable of observables that I use as a commandQueue. This commandQueue has simple observables on it with 1 command I want to send. And I only want to send the next command when I received the "ok" message from the other observable. When I get the "ok" I would complete the simple command observable and it would automatically receive the next value on the commandQueue, being the next command.
My code works only when I send an array of commands, but I want to call the function multiple times, so sending them 1 by 1.
The following code is the function in question, testsubject is an observable to send the next command to the drone.
async send_command_with_return(msg) {
let parentobject = this;
let zeroTime = timestamp();
const now = () => numeral((timestamp() - zeroTime) / 10e3).format("0.0000");
const asyncTask = data =>
new Observable(obs => {
console.log(`${now()}: starting async task ${data}`);
parentobject.Client.pipe(take(1)).subscribe(
dataa => {
console.log("loool")
obs.next(data);
this.testSubject.next(data);
console.log(`${now()}: end of async task ${data}`);
obs.complete();
},
err => console.error("Observer got an error: " + err),
() => console.log("observer asynctask finished with " + data + "\n")
);
});
let p = this.commandQueue.pipe(concatMap(asyncTask)).toPromise(P); //commandQueue is a subject in the constructor
console.log("start filling queue with " + msg);
zeroTime = timestamp();
this.commandQueue.next(msg);
//["streamon", "streamoff", "height?", "temp?"].forEach(a => this.commandQueue.next(a));
await p;
// this.testSubject.next(msg);
}
streamon() {
this.send_command_with_return("streamon");
}
streamoff() {
this.send_command_with_return("streamoff");
}
get_speed() {
this.send_command_with_return("speed?");
}
get_battery() {
this.send_command_with_return("battery?");
}
}
let tello = new Tello();
tello.init();
tello.streamon();
tello.streamoff();
You can accomplish sending commands one at a time by using a simple subject to push commands through and those emissions through concatMap which will execute them one at a time.
Instead of trying to put all the logic in a single function, it will may be easier to make a simple class, maybe call it TelloService or something:
class TelloService {
private commandQueue$ = new Subject<Command>();
constructor(private telloClient: FakeTelloClient) {
this.commandQueue$
.pipe(
concatMap(command => this.telloClient.sendCommand(command))
)
.subscribe()
}
sendCommand(command: Command) {
this.commandQueue$.next(command);
}
}
When the service is instantiated, it subscribes to the commandQueue$ and for each command that is received, it will "do the work" of making your async call. concatMap is used to process commands one at a time.
Consumers would simply call service.sendCommand() to submit commands to the queue. Notice commands are submitted one at a time, it's not necessary to submit an array of commands.
Here is a working StackBlitz example.
To address your condition of waiting until you receive an ok or error response before continuing, you can use takeWhile(), this means it will not complete the observable until the condition is met.
To introduce a max wait time, you can use takeUntil() with timer() to end the stream if the timer emits:
this.commandQueue$
.pipe(
concatMap(command => this.telloClient.sendCommand(command).pipe(
takeWhile(status => !['ok', 'error'].includes(status), true),
takeUntil(timer(3000))
))
)
.subscribe()
Here's an updated StackBlitz.

switch final emitted action from effect based on payload

I have an app that uses ngrx
Once a client updates a product, it uses a websocket to update all clients.
This works, by subscribing to the socket, so after a next method is called on the socket, it calls an action that handles the side effects of updating
But, now when it comes to deleting and adding, I'd like to use the same socket effect but change its final action call
Or if someone can suggest a better way
Socket service:
export class SocketService {
socket$ = Observable.webSocket( 'ws://localhost:1234');
}
effects:
//This is called from component to start the update process
#Effect({dispatch:false}) beginUpdate$: Observable<any> = this.actions$
.ofType<fromBlogActions.BlogUpdateStartAction>(fromBlogActions.BLOG_UPDATE_START_ACTION)
.map((action:any)=>{
console.log(action)
return action.payload;
})
.do((action)=> this.socketService.socket$.next(JSON.stringify(action)))
//Calls the next method to send data to the websocket
//The below watches for data emitted from the websocket
//Then calls the BlogUpdatedAction, what I need is for it to call a different action based on action type
#Effect() watchSocket$ = this.socketService.socket$
.map((action:BlogPayLoad)=>{
console.log(action)
return action
})
.mergeMap((action)=> [new fromBlogActions.BlogUpdatedAction(action)])
It should be possible like this:
#Effect() watchSocket$ = this.socketService.socket$
.map((action:BlogPayLoad)=>{
console.log(action)
return action
})
.mergeMap((action)=> {
if(action.type === 'BlogAddAction'){
return new fromBlogActions.BlogAddAction(action))
else if (...) {
....
}
else if (action.type === 'BlogUpdatedAction'){
return new fromBlogActions.BlogUpdatedAction(action))
})

Why don't we need to subscribe to an observable here before converting it to a Promise?

My Service class contains code as :
Service.ts
//all imports are done
#Injectable()
export class Service{
constructor(private http: Http) { }
getGoogle():Observable<any> {
console.log("Inside service");
return this.http.get('https://jsonplaceholder.typicode.com/posts/1');
}
}
My page component where I make use of service.
Page.ts
//all imports are done
export class Page1{
constructor(private service: Service, private navCtrl: NavController) { }
async get() {
console.log("inside get method");
const data = await this.service.getGoogle().toPromise();
console.log('The response is' , data);
}
}
I have got the required result, but as to understand the concept of Observables and Observer, my Observable should have an Observer sitting to subscribe.Then why should'nt the code const data = await this.service.getGoogle.subscribe().toPromise() does not work here and shows error that property toPromise() does not exists on type Subscription.
I saw the official resource of toPromise() where I found that it is used with .just().toPromise().Then I found .just() API which states that
The just() method emits its parameter(s) as OnNext notifications, and
after that, it emits an OnCompleted notification.
So It is using the features of subscribe here then why it is not used with .subscribe()?
To get the values from an observable, you'll subscribe() on the observable. This starts the observable and it will send you the values it produces.
If you rather want to use a Promise you can call toPromise() on the observable instead. This will make the observable behave like a regular promise.
Under the covers, toPromise() calls subscribe() on the observable and waits for it to send complete(). When the complete() signal is received, the promise will resolve the last emitted next() signal.
toPromise() looks a bit like this:
myObservable.takeLast().subscribe(value => resolve(value));

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