简体   繁体   中英

Better way of RxJS Sequence

How do I simplify this sequence, as I seem to be re-creating the pyramid of doom, and there ought to be a more Rx-y way.

public isEligibleForOffers(): Observable<Boolean> {
    return Observable.create((observer) => {
        this.getAccounts().subscribe(
            (accounts) => {
                if (!this.accountsAreInCredit(accounts)) {
                    observer.next(false);
                } else {
                    this.getOffers(accounts).subscribe(
                        (data: Offers) => {
                            let isEligible = (data.eligible && this.eligibleForAny(data) === true && this.registeredForAny(data) !== true);
                            observer.next(isEligible);
                        }
                    );
                }
            });
    });
}

I need to make an XHR call to get a collection of accounts, and if the accounts are in credit, make another XHR call to get current offers, and if the user is eligible for any offer return true otherwise false.

Essentially

  • Make an initial XHR call
  • When you have the result
  • Make a 2nd XHR call using the results from the 1st call
  • When you have the result
  • Make some final decision.

The difference between what I'm asking and what I've seen on SO is twofold:

  • These async operations are in sequence and non-overlapping
  • The payload from the first response is used in the second

Another similar case might be (1) Get a token, then (2) use the token in a subsequent request.

If you want to chain the observables, use switchMap or flatMap . Also, you do not need to purposely create another Observables using Observable.create() , since your this.getAccounts() is already returning an observable.

This should be more succinct:

public isEligibleForOffers(): Observable<Boolean> {
    return this.getAccounts().switchMap(accounts => {
        if (this.accountsAreInCredit(accounts)) {
            return this.getOffers(accounts)
                .map((data: Offers) => {
                    return (data.eligible && this.eligibleForAny(data) === true && this.registeredForAny(data) !== true);
                })
        }
        //you can be explicit by returning a an Observable of false:
        return Obersvable.of(false);
    })
}

And you can just use it like this:

this.isEligibleForOffers()
    .subscribe(flag => {
        //flag is a boolean
        if(flag){
            //do something
        }else{
            //do something else
        }
    })

Given signatures:

getAccounts(): Account[]
accountsAreInCredit(accounts: Account[]): boolean
getOffers(accounts: Account[]): Offers

You could model your function as follows:

public isEligibleForOffers(): Observable<Boolean> {
  return this.getAccounts()
    .filter(accounts => this.accountsAreInCredit(accounts))
    .mergeMap(
      accounts => this.getOffers(accounts),
      (accounts, offers) => offers.eligible && this.eligibleForAny(offers) && !this.registeredForAny(offers))
    )
    .concat(Rx.Observable.of(false))
    .take(1);
  }  

So if accountsAreInCredit yields false the stream will become empty and we then use the .concat operator to append a default value of false to the stream.

The mergeMap (aka flatMap ) takes an optional argument resultSelector in which you can map the output values so we map them to the isEligibleForOffers boolean value.

Then finally by limiting the amount of values the function isEligibleForOffers can produce we prevent ourselves from emitting true (from the mergeMap ) and false (the default from the concat ) as emissions.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM