Firebase 如何防止我的可观察订阅针对每个发出的值再次运行?

Firebase 如何防止我的可观察订阅针对每个发出的值再次运行?,firebase,google-cloud-firestore,rxjs,observable,Firebase,Google Cloud Firestore,Rxjs,Observable,我有一段很长的代码,其中包含一些嵌套的订阅和事务等 updateActivity(value, index) { return this.currentDay.subscribe(docRef => { let dayRef = this.db.collection('days').doc(docRef[0].payload.doc.id); let innerRef = dayRef.collection('session').doc(index.to

我有一段很长的代码,其中包含一些嵌套的订阅和事务等

  updateActivity(value, index) {
    return this.currentDay.subscribe(docRef => {
      let dayRef = this.db.collection('days').doc(docRef[0].payload.doc.id);
      let innerRef = dayRef.collection('session').doc(index.toString())
      return this.db.firestore.runTransaction(function(transaction) {
        return transaction.get(innerRef.ref).then(function() {
          console.log("outermost")
          innerRef.valueChanges().subscribe(toRollback => {
            console.log("toRollback: " + toRollback)
            dayRef.valueChanges().subscribe(toUpdate => {
              console.log("toUpdate: " + toUpdate)
              dayRef.update({
                caloriesBurned: toUpdate["caloriesBurned"] - toRollback["caloriesBurned"], 
                sessionTime: toUpdate["sessionTime"] - toRollback["duration"] })
            })
          })
          transaction.set(innerRef.ref, value);
        })
      })
    })
  }
要点是这个
dayRef.update()
调用更新Firebase中的一个集合。集合更新后,
dayRef.valueChanges()
订阅将意识到有一个新值要发出并再次运行,它将不断重复更新
dayRef
。这将导致一个无限循环

理想的行为是,我订阅这个可观察对象,然后,使用从可观察对象发出的值,更新可观察对象从中导出其旧值的Firebase集合。由于订阅仍处于活动状态,因此每次更新时,
dayRef.update()
调用将继续执行

我理解这可能是对可观测事物的根本误解。从逻辑上讲,这里发生的事情对我来说是有意义的,但我不确定在不订阅的情况下如何使用这些值,也不确定如何正确地取消订阅。我已经尝试在每个可观察的块的末尾运行
.unsubscribe()
,但这只是在值发出之前同步取消订阅

编辑:使用
switchMap

dayRef.valueChanges().pipe(switchMap(toUpdate => 
              innerRef.valueChanges().pipe(switchMap(toRollback => 
                dayRef.update({
                  caloriesBurned: toUpdate["caloriesBurned"] - toRollback["caloriesBurned"], 
                  sessionTime: toUpdate["sessionTime"] - toRollback["duration"] })
            ))
          ))

编写嵌套订阅确实是一种非常糟糕的做法。而是链接rxjs操作符。 在您的示例中,我认为switchMap非常适合。取决于你想要完成什么

见文件:

编辑:

此外,尝试在事务之前定义大部分可观察的逻辑。如果可以,请在
updateActivity()
函数之外定义它

例如:

        let dayRef$ = this.currentDay.pipe(
            switchMap(docRef => this.db.collection('days').doc(docRef[0].payload.doc.id))
        );
        let innerRef$ = dayRef$.pipe(
            map(dayRef => dayRef.collection('session').doc(index.toString()))
        );

表示您的观察值。

在事务中使用
valueChanges
听起来不是个好主意。如果您需要在事务中处理文档,您应该只使用传递给事务处理程序的
事务
对象。嘿,我已经尝试过了,但我看不出switchMap有什么真正的区别。当我订阅内部使用switchMap的管道时,同样的无限循环效果也会出现。我已经在我的答案中添加了代码,但我仍然不确定我是否正确使用了switchMap。我想你还不明白它是如何工作的。当您从一个可观察对象订阅时,您将切换到一个同步上下文。因此,在subscribe中执行的代码在发出的每个新值处执行。您的第一段代码在
innerRef.valueChanges()
的每个新值处为您的
dayRef.valueChanges()
创建一个新订阅。你能看到问题所在吗?我已经将这里的一些处理带到了一个运行得更好的云函数。该函数几乎按预期工作,但它仍然卡在与第一次订阅相关的更新循环中。如您所示,我尝试使用switchMap获取dayRef,但是
.doc
没有返回可观察值,因此操作员抱怨。我尝试改用
map
,但这仍然会产生相同的效果。之后我仍然必须订阅
dayRef$
,因此每次我更新
day
集合时,它仍然会发出一个新值。请尝试在整个过程中只使用一个子项。如果您有这样的能力,不仅可以轻松跟踪您的流程,还可以解决许多错误。适应这种看待事物的方式需要一些时间,但一旦你做到了,这是值得的。