0

I have two methods in which I'm doing some asynchronous operation but I don't want method 2 to execute until or unless the response from method 1 is positive, to indicate that we can proceed further.

So, this is what I tried:

Method 1:

private method1(): Observable<any> {
  return new Observable(() => {
    executingSomething();
    this.anotherSubscription.pipe(
      map(x => {
        console.log('Control is not reaching here');
        Also, how to indicate the caller that method2 can be executed?
        I can't return anything since I'm already returning the new Observable, above.
      })
    );
  });
}

Caller:

concat(this.method1(), this.method2()).subscribe();

Issue: anotherSubscription is not even getting executed and I can't think of any way to pass a response from within anotherSubscription to the caller.

I feel that I'm not using the observables in correct sense, here but can't seem to find anything anywhere.

Suyash Gupta
  • 546
  • 10
  • 31
  • This is also closely related: https://stackoverflow.com/questions/50452947/rxjs-conditional-switchmap-based-on-a-condition. – Avius Dec 23 '20 at 12:29

3 Answers3

1

I've made some assumptions regarding your code prior to writing the solution. If the assumptions are wrong, then the answer may be as well.

  1. executingSomething() is a synchronous method with unrelated functionality, except that you want the whole thing to fail if it throws.
  2. anotherSubscription is not a Subscribtion but an Observable (these are different things).

Here's how I'd solve your problem:

class SomeClass {
  private method1(): Observable<any> {
    try {
      executingSomething();
    } catch (err) {
      // This would return an errorred observable, which is great, since
      // you can still subscribe to it (no need to change the return type
      // of this method).
      return throwError(err);
    }

    return this.anotherSubscription.pipe(
      tap(() => {
        console.log('Controls is reaching here!');
      }),
    );
  }

  private method2(): Observable<any> {
    // This can be whatever obervable, using of(null) for demo purposes.
    return of(null);
  }

  private parentMethod() {
    this.method1()
      .pipe(
        switchMap(valueFromAnotherSubscription => {
          // UPD1 - Implement your custom check here. This check will determine
          // whether `method2` will be called.
          if (valueFromAnotherSubscription === targetValue) {
            return this.method2();
          } else {
            // UPD1 - If the check evaluates to `false`, reemit the same value
            // using the `of` operator.
            return of(valueFromAnotherSubscription);
          }
        }),
      )
      .subscribe(() => {
        console.log('Done!');
      });
  }
}

The key operator here, as correctly indicated by Kevin, is switchMap. Every time an observable (anotherSubscription) emits, switchMap will cancel it and subscribe to a different observable (whatever is returned from method2).

This happens sequentially so method2 will only be subscribed to after method1 emits. If method1 throws, the whole pipeline will fail. You may also filter the results of method1 to decide whether or not you want to switch to method2.

Also, constructing observables using new Observable is probably not needed in most cases. I have a rather large RxJS application and have never used this so far.

Update 1

See code denoted with UPD1 comments.

Keep in mind that if an error is thrown by anotherSubscription, the switchMap function will not be called anyway.

Avius
  • 5,504
  • 5
  • 20
  • 42
  • Any specific reason you used `tap` instead of `map` ? – Suyash Gupta Dec 23 '20 at 10:04
  • Yes, `map`'s purpose is returning a different value for every emitted value. Conversely, `tap` is used for side-effects. I consider a `log` statement to be a side-effect, so `tap` is more appropriate. But you may do this inside a `map` statement as well - it will work, just don't forget to return your value : ] – Avius Dec 23 '20 at 10:21
  • One more thing. How do I filter my response from method1? In fact, how do I return something (such as a boolean to indicate whether to proceed further or not) when I'm returning `Observable`? I tried returning `of(false)` but I can't use that `false` until I subscribe to it and I'd need to do it in `switchMap(res => { if (res) { } )`, else it would execute method2. – Suyash Gupta Dec 23 '20 at 11:11
  • OK, so what's clear to me is that whenever `method1` emits the "correct" value, you want the subscriber to receive a value from `method2`. What's not clear is what should happen when `method1` emits a "bad" value. I see two possible scenarios, tell me which one you need: A) when `method1` emits a "bad" value, you want to completely ignore such values, so the subscriber is not alerted at all; B) when `method1` emits a "bad" value, you simply want the subscriber to receive that value, whilst skipping `method2`. I hope the difference is clear. I have updated the answer using option B. – Avius Dec 23 '20 at 11:58
  • Ah, I was looking at some wrong console output and got confused. Thank you for your help. This works. – Suyash Gupta Dec 23 '20 at 12:09
0

You can explicitly return the value of the serviceN to the serviceN+1. Here's the idea :

private setupStuff() {
  this.initRouteParams()
    .pipe(
      switchMap(serviceId => {
        return zip(of(serviceId), this.getFileInfo(serviceId))
      }),
      switchMap(([serviceId, filename]) => {
        return zip(of(serviceId), of(filename), this.getExistingFile(serviceId, filename))
      })
    )
    .subscribe(([serviceId, filename, response]) => {
      console.log(serviceId, filename, response);
    })
}
Kevin Zhang
  • 1,012
  • 6
  • 14
  • What I'm asking and what you're suggesting are completely different. I want to return some response from within an observable, which is nested within another observable. And on top of that, inner observable is not even getting executed. – Suyash Gupta Dec 23 '20 at 09:06
  • Just write a single example, is this satisfy you requirement? https://stackblitz.com/edit/angular-ivy-rn5kfe?file=src/app/app.component.ts – Kevin Zhang Dec 23 '20 at 09:40
0

"I don't want method 2 to execute until or unless the response from method 1 is positive"

If you just want the emission from obs2, to be triggered after the emission of obs1 (no matter what obs1 emits), just use a switchMap:

fromEvent(document, 'click')
  .pipe(
    switchMap(() => of([1]))
  )
  .subscribe(console.log); <--- will emit [1] only after the user clicks.

If you need that obs2 only emits after obs1 emits a specific value, use switchMap with a filter operator (https://www.learnrxjs.io/learn-rxjs/operators/filtering/filter):

const obs$ = of(true); 
const obs2$ = of([1,2,3]);

    obs$.pipe(
        filter((result) => result === true),
        switchMap(() => obs2$)
      ).subscribe(console.log) <--- returns [1,2,3] since filter condition returns true

if you need something like an If Else statement, use the conditional operator IIF (https://www.learnrxjs.io/learn-rxjs/operators/conditional/iif):

const even$ = of('even');
const odd$ = of('odd');

interval(1000).pipe(
  mergeMap(v =>
    iif(
      () => v % 2 === 0,
      even$,
      odd$
    ))
).subscribe(console.log); <--- will emit every second a string "even" or "odd" based on the condition.
GBra 4.669
  • 1,512
  • 3
  • 11
  • 23