javascript - Hur skriver man inbäddad subscribe på ett renare sätt?


Jag är ny med RxJS och jag vill lära mig hur man skriver kod med det på ett rent sätt. Jag har nästlade prenumerationer och jag har försökt skriva om detta, men utan resultat.

firstMethod() {
  this.testMethod(name)
  console.log(this.currentPerson)
}

testMethod() {
  this.myService.search(name).subscribe(response => {
    if(result) {
      this.currentPerson = result
    } else {
        this.myService.create(name).subscribe(result => {
          const person = {id: result.id, name: name}
          this.currentPerson = person
        })
      }
   })
}

Tyvärr, trots att koden är rörig, är det något fel på en del efter 'else' eftersom console.log visar undefined. Några tips på hur man fixar det?

Svar #1

För att effektivt hantera nästlade subscribes bör du använda en av "Higher Order Mapping Operators", som gör några bra saker för dig:

  1. kartlägger det inkommande värdet till en annan observerbar
  2. prenumererar på det, så dess värden skickas ut i strömmen
  3. hanterar automatiskt att avsluta prenumerationer från dessa "inre prenumerationer"

I detta fall är switchMap ett bra val eftersom det bara tillåter en enda intern prenumeration åt gången, så när myService.search(name) anropas skapas en ny intern prenumeration för myService.create(name), och den föregående avregistreras automatiskt.

@Rafi Henigs svar visar ett gott exempel på hur detta skulle kunna se ut.

  • Notera användningen av .pipe(). Du kan definiera omvandlingar till din observerbara utdata med hjälp av strömlinjeformade operatorer utan att faktiskt prenumerera.

Jag skulle föreslå att du inte prenumererar i din testMethod(), utan istället returnerar en observabel. Låt oss också ge "testMethod()" ett mer meningsfullt namn för vidare diskussion: "getPerson()".

getPerson(name: string): Observable<Person> {
  return this.myService.search(name).pipe(
    switchMap(result => {
      return iif(
        () => result,
        of(result),
        this.myService.create(name).pipe(
          map(({ id }) => ({ id, name }))
        )
      )
    }),
    tap(person => this.currentPerson = person)
  );
}

console.log visar undefined. Några tips på hur man fixar det?

1  firstMethod() {
2    this.getPerson(name)
3    console.log(this.currentPerson)
4  }

Anledningen till undefined är eftersom koden är asynkron. Rad 2 körs, sedan rad 3 direkt efter, men det asynkrona arbetet har ännu inte slutförts, så this.currentPerson har ännu inte satts.

eftersom vår getPerson()-metod nu returnerar ett observerbart värde, kan vi prenumerera och göra din console.log() inuti prenumerationen:

1  firstMethod() {
2    this.getPerson(name).subscribe(
3       () => console.log(this.currentPerson)
4    )
5  }

För att förenkla behöver vi inte ens this.currentPerson längre, eftersom personen skickas genom strömmen!

1  firstMethod() {
2    this.getPerson(name).subscribe(
3       person => console.log(person)
4    )
5  }

Och eftersom du vill...

lär dig hur man skriver kod med det på ett rent sätt

Jag tror att det renaste sättet förmodligen skulle vara att definiera din "personresultat" som en observabel och slänga this.currentPerson.

person$ = this.getPerson(name);

Så nu har du this.person$ som kan abonneras på och alltid kommer att ha det aktuella värdet av person. Ingen behöver "manuellt" uppdatera this.currentPerson.

Nja... nästan. Vi behöver tänka på vad som händer när söktermen ändras.

Låt oss anta att sökordet "name" kommer från ett formulärkontrollfält.

När man använder Reactive Forms är inmatningsvärdet en observerbar källa, så vi kan definiera vår person$ från söktermen:

searchTerm$ = this.searchInput.valueChanges();

person$ = this.searchTerm$.pipe(
  switchMap(searchTerm => this.getPerson(searchTerm))
);

getPerson(name: string): Observable<Person> {
  return this.myService.search(name).pipe(
    switchMap(result => {
      return iif(
        () => result,
        of(result),
        this.myService.create(name).pipe(
          map(({ id }) => ({ id, name }))
        )
      )
    })
  );
}

Observera att vi har definierat två olika observables, men vi har inte prenumererat än! Nu kan vi använda async-piped i vår mall för att hantera prenumerationen, vilket håller vår komponentkod enkel och lätt.

<p *ngIf="person$ | async as person">
  We found {{ person.name }} !
</p>

Jag vet att det här har blivit lite långrandigt, men jag hoppas att du ser hur det är möjligt att transformera utdata med pipbara operatorer och hur du kan definiera ett observable från ett annat.

Svar #2

Använd switchMap för att returnera ett nytt Observable baserat på värdet av result med hjälp av IIF-operatören som visas nedan:

this.myService.search(name)
  .pipe(
    switchMap(result => {
      return iif(
        () => result,
        of(result),
        this.myService.create(name).pipe(map(({ id }) => ({ id, name })))
      )
    })
  )
  .subscribe(person => {

  })

Eller alternativt:

this.myService.search(name)
  .pipe(
    switchMap(result => {
      if (result) return of(result);
      else this.myService.create(name).pipe(map(({ id }) => ({ id, name })));
    })
  )
  .subscribe(person => {

  })

Relaterade artiklar