如何以更简洁的方式编写嵌套订阅?

时间:2020-09-02 18:11:46

标签: javascript angular typescript rxjs

我是RxJS的新手,我想学习如何以干净的方式使用它编写代码。我已经嵌套了订阅,并且尝试重写它,但是没有结果。

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
        })
      }
   })
}

不幸的是,尽管代码很混乱,但在“ else”之后的某些部分还是有问题,因为console.log显示未定义。 有提示如何解决吗?

2 个答案:

答案 0 :(得分:3)

要有效地处理嵌套订阅,您应该使用“ Higher Order Mapping Operator”之一,它可以为您带来一些好处:

  1. 将输入值映射到另一个可观察值
  2. 订阅它,因此它的值被发送到流中
  3. 自动取消订阅这些“内部订阅”

在这种情况下,switchMap是一个不错的选择,因为它一次只允许一个内部订阅,因此,每当调用myService.search(name)时,都会为myService.create(name)创建一个新的内部订阅,而前一个将自动取消订阅。

@Rafi Henig的答案很好地说明了这种情况。

  • 注意使用.pipe()。您可以使用管道运算符定义对可观察输出的转换,而无需实际订阅。

我建议您不要订阅testMethod(),而是返回一个可观察的结果。另外,让我们给“ testMethod()”一个更有意义的名称以供进一步讨论:“ 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显示未定义。有提示如何解决吗?

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

使用undefined的原因是因为代码是异步的。先执行第2行,然后执行第3行,但异步工作尚未完成,因此尚未设置this.currentPerson

由于我们的getPerson()方法现在返回了一个可观察值,因此我们可以订阅并在订阅中执行您的console.log()

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

为简化起见,我们甚至不再需要this.currentPerson,因为此人是通过流发出的!

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

既然你想...

学习如何以简洁的方式编写代码

我认为最干净的方法可能是将“人员结果”定义为可观察到的this.currentPerson

person$ = this.getPerson(name);

所以现在您有this.person$可以订阅,并且将始终具有person的当前值。无需“手动”更新this.currentPerson

好吧...差不多。我们需要考虑一下搜索词发生变化时会发生什么情况。

我们假设搜索词“名称”来自表单控件输入。

使用Reactive Forms时,输入值是一个可观察的来源,因此我们可以从搜索词中定义person$

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 }))
        )
      )
    })
  );
}

注意,我们已经定义了两个不同的可观察对象,但是我们还没有订阅!现在,我们可以利用模板中的async管道来处理订阅,从而使我们的组件代码简洁明了。

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

我知道这已经花了很长的时间了,但是我希望您能看到使用管道运算符转换输出的可能性,以及如何定义另一个可观察变量。

答案 1 :(得分:2)

使用switchMap使用IIF operator根据result的值返回一个新的Observable,如下所示:

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

  })

或可选:

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

  })