【问题标题】:How to write nested subscribe in cleaner way?如何以更简洁的方式编写嵌套订阅?
【发布时间】:2020-09-02 18:11:46
【问题描述】:

我是 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 显示未定义。 有什么解决办法吗?

【问题讨论】:

标签: javascript angular typescript rxjs


【解决方案1】:

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

  1. 将传入的值映射到另一个 observable
  2. 订阅它,所以它的值被发送到流中
  3. 自动管理从这些“内部订阅”中退订

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

@Rafi Henig 的回答展示了一个很好的例子来说明这可能是什么样子。

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

我建议你不要订阅 testMethod(),而是返回一个 observable。另外,让我们给“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() 方法现在返回一个 observable,我们可以订阅并在订阅中执行您的 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 }))
        )
      )
    })
  );
}

请注意,我们已经定义了两个不同的 observable,但我们还没有订阅!现在,我们可以利用模板中的async 管道来处理订阅,让我们的组件代码保持简洁。

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

我知道这有点冗长,但我希望您了解如何使用可管道运算符转换输出,以及如何定义一个可观察对象。

【讨论】:

    【解决方案2】:

    使用switchMap 根据result 的值使用IIF operator 返回一个新的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 => {
    
      })
    

    【讨论】:

    • 虽然上面的答案对于共享的示例是正确的,但请注意您要执行的操作。 SwitchMap 将自动取消订阅前一个 observable 并订阅另一个(给定控制流)。在共享的示例中,这似乎是您正在尝试做的事情(假设您正在更新单个属性)。然而,在某些情况下,您可能希望保留每个可观察对象,例如,mergeMap 会更好。看看@BizzyBob 的回答。已经很完整了
    猜你喜欢
    • 2021-11-17
    • 1970-01-01
    • 1970-01-01
    • 2019-04-02
    • 2019-12-07
    • 2022-01-08
    • 2021-03-24
    • 2011-04-23
    • 1970-01-01
    相关资源
    最近更新 更多