return observable but wait until another finishes

Issue

I was ask to add a functionnality to a legacy code. The legacy code handle a get request with some added features like avoiding duplciate calls and cache get request when PWA is offline.

In the context of this PWA when have some POST/PUT/DELETE operation I want to commit before any get is executed.
I made an observable $processingOfflineOperation which return true or false and I want to delay any get operation until $processingOfflineOperation next to false.

this is the get method :

public get(endpoint: string, params?: any, avoidCache?: boolean, reqOpts?: any, ignoreHTTPStatusCode?: HttpStatusCode[]): Observable<any> {
    const pendingObs = this.checkIfPendingToAvoidDuplicateCalls('GET ' + endpoint, params);
    if (!!pendingObs) {
      // return duplicate call
      return pendingObs;
    }
    if (!reqOpts) {
      reqOpts = {
        params: new HttpParams()
      };
    }

    let header = new HttpHeaders({Authorization: this.getAuthorizationHeaderValue()});
    header = header.set('Content-type', 'application/json');
    reqOpts.headers = header;
    endpoint = (endpoint) ? endpoint : '';
    if (!this.network.online && !avoidCache) {
      // return from cache
      return of(this.cacheCalls['GET ' + endpoint + ' ' + JSON.stringify(params)]);
    } else if (!this.network.online) {
      return throwError(() => new Error('No internet'));
    }
    const $obs = this.http.get(this.url + endpoint + '?' + this.JSON_to_URLEncoded(params), reqOpts).pipe(
      catchError((err) => {
        this.clearPendingCall('GET ' + endpoint, params);
        if (!!ignoreHTTPStatusCode && !!ignoreHTTPStatusCode.find((status) => status === err.status)) {
          return of(null);
        }
        this.manageError(err);
        return throwError(err);
      }),
      map(res => {
        this.clearPendingCall('GET ' + endpoint, params);
        this.addCacheCall('GET ' + endpoint + ' ' + JSON.stringify(params), res);
        return res;
      }),
      share()
    );
    this.setPendingCall('GET ' + endpoint, params, $obs);
    return $obs;
  }

I have no idea how to proceed all my attempt ended changing the behaviour of the get method
My best guess was this but this is not working as excpected :

public get(endpoint: string, params?: any, avoidCache?: boolean, reqOpts?: any, ignoreHTTPStatusCode?: HttpStatusCode[]): Observable<any> {


    const $subject = new Subject();
    this.$processingOfflineOperation.subscribe(isProcessing => {
      if (isProcessing) {
        return ;
      }

      const pendingObs = this.checkIfPendingToAvoidDuplicateCalls('GET ' + endpoint, params);
      if (!!pendingObs) {
        // return duplicate call
        $subject.next(pendingObs);
      }
      if (!reqOpts) {
        reqOpts = {
          params: new HttpParams()
        };
      }

      let header = new HttpHeaders({Authorization: this.getAuthorizationHeaderValue()});
      header = header.set('Content-type', 'application/json');
      reqOpts.headers = header;
      endpoint = (endpoint) ? endpoint : '';
      if (!this.network.online && !avoidCache) {
        // return from cache
        $subject.next(this.cacheCalls['GET ' + endpoint + ' ' + JSON.stringify(params)]);
      } else if (!this.network.online) {
        $subject.error(new Error('No internet'));
        return throwError(() => new Error('No internet'));
      }
      const $obs = this.http.get(this.url + endpoint + '?' + this.JSON_to_URLEncoded(params), reqOpts).pipe(
        catchError((err) => {
          this.clearPendingCall('GET ' + endpoint, params);
          if (!!ignoreHTTPStatusCode && !!ignoreHTTPStatusCode.find((status) => status === err.status)) {
            return of(null);
          }
          this.manageError(err);
          $subject.error(err);
          return throwError(err);
        }),
        map(res => {
          this.clearPendingCall('GET ' + endpoint, params);
          this.addCacheCall('GET ' + endpoint + ' ' + JSON.stringify(params), res);
          $subject.next(res);
        }),
        share()
      );
      this.setPendingCall('GET ' + endpoint, params, $obs);
      $obs.subscribe();
    });
    return $subject;
  }

Following @dmance comment I tried to rename the first version of the get into getReal and created a new function "get" then I did this :

  public get(endpoint: string, params?: any, avoidCache?: boolean, reqOpts?: any, ignoreHTTPStatusCode?: HttpStatusCode[]): Observable<any> {

    return this.getReal(endpoint, params, avoidCache, reqOpts, ignoreHTTPStatusCode).pipe(
  filter(() => !this.isProcessing), 
  take(1)
);
  } 

but still no chance,
then this :

public get(endpoint: string, params?: any, avoidCache?: boolean, reqOpts?: any, ignoreHTTPStatusCode?: HttpStatusCode[]): Observable<any> {
    const $subject = new Subject();
    this.getReal(endpoint, params, avoidCache, reqOpts, ignoreHTTPStatusCode).pipe(
      skipUntil(this.$processingOfflineOperation.pipe(filter(isProcessing => {
        return isProcessing === false;
      })))
    ).subscribe({
      next: (v) => {
        $subject.next(v);
      },
      error: (err) => {
        $subject.error(err);
      }
    });
    return $subject;
  }

not working either.

Not sure this is the most elegant way to do it, but this one works :

 public get(endpoint: string, params?: any, avoidCache?: boolean, reqOpts?: any, ignoreHTTPStatusCode?: HttpStatusCode[]): Observable<any> {
    const $subject = new Subject();
    this.$processingOfflineOperation.subscribe((isProcessing) => {
      if (!isProcessing) {
        this.getReal(endpoint, params, avoidCache, reqOpts, ignoreHTTPStatusCode).subscribe({
          next: (v) => {
            $subject.next(v);
          },
          error: (err) => {
            $subject.error(err);
          }
        });
      }
    });
    return $subject;
  }

Solution

Not sure this is the most elegant way to do it, but this one works :

 public get(endpoint: string, params?: any, avoidCache?: boolean, reqOpts?: any, ignoreHTTPStatusCode?: HttpStatusCode[]): Observable<any> {
    const $subject = new Subject();
    this.$processingOfflineOperation.subscribe((isProcessing) => {
      if (!isProcessing) {
        this.getReal(endpoint, params, avoidCache, reqOpts, ignoreHTTPStatusCode).subscribe({
          next: (v) => {
            $subject.next(v);
          },
          error: (err) => {
            $subject.error(err);
          }
        });
      }
    });
    return $subject;
  }

Elegant

You should be able to re-write this without an intermediate Subject. I can’t test this for you, but something like this should work:

public get(
  endpoint: string, 
  params?: any, 
  avoidCache?: boolean, 
  reqOpts?: any, 
  ignoreHTTPStatusCode?: HttpStatusCode[]
): Observable<any> {
  return this.$processingOfflineOperation.pipe(

    mergeMap(isProcessing => isProcessing ? EMPTY : 
      this.getReal(endpoint, params, avoidCache, reqOpts, ignoreHTTPStatusCode)
    )

  );
}

You’ll notice that by not managing your own subscriptions, the code becomes much shorter/concise. In general, nested subscribe blocks are a code smell. There’s most often a better way.

Answered By – Mrk Sef

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published