0

I have a angular 5 app with the rxjs WebsocketSubject sending jsonrpc messages.

This is my sendRequest function

sendRequest(request: Request): Promise<Response> {

  console.log(request);

  this.socket.next(JSON.stringify(request));

  return this.onResponse().filter((response: Response) => {
    return response.id === request.id;
  }).first().toPromise().then((response) => {

    console.log(response);

    if (response.error) {
      console.log('error');
      throw new RpcError(response.error);
    }

    return response;

  });

}

I am using the first() operator to complete this filter subscription. But onResponse() comes directly from my WebsocketSubject and this will then be completed.

Are there any methods for decoupling the original subject?

Or should I create a new Observale.create(...)?

What happens with the written .filter function. Does it last anywhere or do I have to remove it anywhere preventing ever lasting filter calls?


Edit 1

Also using this does not help.

sendRequest(request: Request): Promise<Response> {

  console.log(request);

  this.socket.next(JSON.stringify(request));

  return new Promise<Response>((resolve, reject) => {

    const responseSubscription = this.onResponse().filter((response: Response) => {
      console.log('filter');
      return response.id === request.id;
    }).subscribe((response: Response) => {

      // responseSubscription.unsubscribe();
      resolve(response);

    });

  });

}

If I execute the unsubscribe the whole websocketSubject is closed. Not doing so logs 'filter' on time more per request !!


Edit 2

Here is the whole websocketService i have written

import {Injectable} from "@angular/core";
import {WebSocketSubject, WebSocketSubjectConfig} from "rxjs/observable/dom/WebSocketSubject";
import {MessageFactory, Notification, Request, Response, RpcError} from "../misc/jsonrpc";
import {ReplaySubject} from "rxjs/ReplaySubject";
import {Observable} from "rxjs/Observable";
import 'rxjs/add/operator/toPromise';
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/first';
import 'rxjs/add/observable/from';

export enum ConnectionState {
  CONNECTED = "Connected",
  CONNECTING = "Connecting",
  CLOSING = "Closing",
  DISCONNECTED = "Disconnected"
}

@Injectable()
export class WebsocketService {

  private connectionState = new ReplaySubject<ConnectionState>(1);
  private socket: WebSocketSubject<ArrayBuffer | Object>;
  private config: WebSocketSubjectConfig;

  constructor() {

    console.log('ctor');

    const protocol = location.protocol === 'https' ? 'wss' : 'ws';
    const host = location.hostname;
    const port = 3000; // location.port;

    this.config = {
      binaryType: "arraybuffer",
      url: `${protocol}://${host}:${port}`,
      openObserver: {
        next: () => this.connectionState.next(ConnectionState.CONNECTED)
      },
      closingObserver: {
        next: () => this.connectionState.next(ConnectionState.CLOSING)
      },
      closeObserver: {
        next: () => this.connectionState.next(ConnectionState.DISCONNECTED)
      },
      resultSelector: (e: MessageEvent) => {

        try {

          if (e.data instanceof ArrayBuffer) {
            return e.data;
          } else {
            return JSON.parse(e.data);
          }

        } catch (e) {

          console.error(e);
          return null;

        }

      }
    };

    this.connectionState.next(ConnectionState.CONNECTING);
    this.socket = new WebSocketSubject(this.config);

    this.connectionState.subscribe((state) => {
      console.log(`WS state ${state}`);
    });

  }

  onBinaryData(): Observable<ArrayBuffer> {
    return this.socket.filter((message: any) => {
      return message instanceof ArrayBuffer;
    });
  }

  onMessageData(): Observable<Object> {
    return this.socket.filter((message: any) => {
      return !(message instanceof ArrayBuffer);
    });
  }

  onResponse(): Observable<Response> {
    return this.onMessageData().filter((message) => {
      return MessageFactory.from(message).isResponse();
    }).map((message): Response => {
      return MessageFactory.from(message).toResponse();
    });
  }

  sendRequest(request: Request): Promise<Response> {

    console.log(request);

    this.socket.next(JSON.stringify(request));

    return new Promise<Response>((resolve, reject) => {

      const responseSubscription = this.onResponse().filter((response: Response) => {
        console.log('filter');
        return response.id === request.id;
      }).subscribe((response: Response) => {

        responseSubscription.unsubscribe();
        resolve(response);

      });

    });

  }

  sendNotification(notification: Notification): void {
    this.socket.next(JSON.stringify(notification));
  }

}

And the result in my log

Using Angular 5.0.2
websocket.service.ts:27 ctor
websocket.service.ts:69 WS state Connecting
core.js:3565 Angular is running in the development mode. Call enableProdMode() to enable the production mode.
websocket.service.ts:96 Request {jsonrpc: "2.0", id: "b042005c-5fbf-5ffc-fbd1-df68fae5882e", method: "appointment_list_get", params: undefined}
websocket.service.ts:69 WS state Connected
websocket.service.ts:103 filter
websocket.service.ts:69 WS state Disconnected

I need to find a way decoupling my filter from the original stream somehow.

Pascal
  • 2,059
  • 3
  • 31
  • 52
  • Why you even need to use `first().toPromise().then`? Isn't it easier to just subscribe like `this.onResponse().filter(...).subscribe(...);`? – martin Dec 03 '17 at 08:50
  • Yes but in my understanding (maybe i am wrong) i have to cleanup that subscription, and I am expecting a singleresult. So using a Promise should be fine. But .toPromise only resolves on Observalbe complete – Pascal Dec 03 '17 at 08:56
  • Then what's the problem with the way you're doing it right now? – martin Dec 03 '17 at 09:07
  • .first() completes the whole stream. So my websocketSubject is completed because onResponse() returns a filtered Observable from my websocketSubject. So my websocket is closed after every resonse – Pascal Dec 03 '17 at 09:15
  • Completing the chain doesn't complete the subject in `this.onResponse()` (or whatever it is). From what you showed in the edit the problem will be someplace else and not in completing RxJS chains. – martin Dec 03 '17 at 09:20

1 Answers1

1

This is working. The key was to decouple the message handling from the underlaying websocketSubject.

import {Injectable} from "@angular/core";
import {WebSocketSubject, WebSocketSubjectConfig} from "rxjs/observable/dom/WebSocketSubject";
import {MessageFactory, Notification, Request, Response, RpcError} from "../misc/jsonrpc";
import {ReplaySubject} from "rxjs/ReplaySubject";
import {Observable} from "rxjs/Observable";
import 'rxjs/add/operator/toPromise';
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/first';
import 'rxjs/add/observable/from';
import {Subject} from "rxjs/Subject";

export enum ConnectionState {
  CONNECTED = "Connected",
  CONNECTING = "Connecting",
  CLOSING = "Closing",
  DISCONNECTED = "Disconnected"
}

@Injectable()
export class WebsocketService {

  private connectionState = new ReplaySubject<ConnectionState>(1);
  private socket: WebSocketSubject<ArrayBuffer | Object>;
  private config: WebSocketSubjectConfig;

  private messageObserver = new Subject<MessageFactory>();
  private binaryObserver = new Subject<ArrayBuffer>();

  constructor() {

    const protocol = location.protocol === 'https' ? 'wss' : 'ws';
    const host = location.hostname;
    const port = 3000; // location.port;

    this.config = {
      binaryType: "arraybuffer",
      url: `${protocol}://${host}:${port}`,
      openObserver: {
        next: () => this.connectionState.next(ConnectionState.CONNECTED)
      },
      closingObserver: {
        next: () => this.connectionState.next(ConnectionState.CLOSING)
      },
      closeObserver: {
        next: () => this.connectionState.next(ConnectionState.DISCONNECTED)
      },
      resultSelector: (e: MessageEvent) => {

        try {

          if (e.data instanceof ArrayBuffer) {
            return e.data;
          } else {
            return JSON.parse(e.data);
          }

        } catch (e) {

          console.error(e);
          return null;

        }

      }
    };

    this.connectionState.next(ConnectionState.CONNECTING);
    this.socket = new WebSocketSubject(this.config);

    this.socket.filter((message: any) => {
      return message instanceof ArrayBuffer;
    }).subscribe((message: ArrayBuffer) => {
      this.binaryObserver.next(message);
    });

    this.socket.filter((message: any) => {
      return !(message instanceof ArrayBuffer);
    }).subscribe((message: ArrayBuffer) => {
      this.messageObserver.next(MessageFactory.from(message));
    });

    this.connectionState.subscribe((state) => {
      console.log(`WS state ${state}`);
    });

  }

  onResponse(): Observable<Response> {
    return this.messageObserver.filter((message: MessageFactory) => {
      return message.isResponse();
    }).map((message: MessageFactory): Response => {
      return message.toResponse();
    });
  }

  sendRequest(request: Request): Promise<Response> {

    console.log(request);

    this.socket.next(JSON.stringify(request));

    return this.onResponse().filter((response: Response) => {
      return request.id === response.id;
    }).first().toPromise().then((response) => {

      console.log(response);

      if (response.error) {
        console.log('error');
        throw new RpcError(response.error);
      }

      return response;

    });

  }

  sendNotification(notification: Notification): void {
    this.socket.next(JSON.stringify(notification));
  }

}
Pascal
  • 2,059
  • 3
  • 31
  • 52