Firestore 活动文档快照侦听器中断排序

Firestore active document snapshot listener breaks sorting

我 运行 遇到了一个 firestore 问题,希望有人能帮我解决。

我有一个活动的文档快照侦听器,它似乎破坏了排序行为,我不确定为什么。

在我的组件的构造函数中,我初始化了一次文档快照监听器:

this.listen = this.fs.collection('users').doc('0EwcWqMVp9j0PtNXFDyO').ref.onSnapshot(t => {
  console.log(t)
})

每当我单击“排序”按钮时,它都会初始化一个按名字排序的新集合查询。

  async onSort() {
    if (this.first) {
      this.first();
    }

    this.sort = this.sort === 'asc'? 'desc' : 'asc';    
    alert(this.sort)
    let arr = []
    let lastDoc = null
    let queryRef = this.fs.firestore.collection('users').orderBy('firstName', this.sort as any).limit(20);
    this.users$ = this._users$.asObservable()

    // initialize a blank list
    this._users$.next([])

    // subscribe to user list snapshot changes
    const users1: { users: any, last: any } = await new Promise((resolve, reject) => {
      this.first = queryRef.onSnapshot((doc) => {
        console.log("Current data: ", doc);
        
        const users = doc.docs.map(t => {
          return {
            id: t.id,
            ...t.data() as any
          }
        })
        console.log(users)
        resolve({
          users: users,
          last: doc.docs[doc.docs.length -1]
        })
      });
  
    })
    this._users$.next(this._users$.value.concat(users1.users))

  }

但是,这没有按预期工作。排序是return一条记录(根据限制应该有20条记录)。

有趣的是,取消订阅文档快照,修复了排序。正在 return 编辑 20 条记录,并且排序正常。

谁能解释一下我做错了什么?

这是重现问题的最小 Stackblitz。

Demo of Issue

[编辑]

越来越接近解决方案和理解... 感谢@ZackReam 的解释以及@Robin 和@Zack 的规范答案。

似乎 this._firestore.firestore.collection('users').orderBy('firstName', this.sort as any).limit(20).onSnapshot(...) 确实发出了两次 - 一次是针对活动文档侦听器(在缓存中有 1 条记录),第二次是针对已排序的集合(因为它不在缓存中) switchMap 将在每次执行 sort 时取消订阅。

我们可以在 Zack 发布的功能演示中简单地看到 - 它闪烁了一条与文档侦听器对应的记录,然后一瞬间,列表中填充了排序的集合。

你的听众越活跃,结果的闪现就会越奇怪。这是设计使然吗?对我来说似乎有缺陷...

直觉上,我希望 snapshotChanges()valueChanges()this._firestore.firestore.collection('users').orderBy('firstName', this.sort as any).limit(20) 查询到 return 20 个结果(排序后的前 20 个),独立于任何其他文档听众。违反直觉的是,此查询的第一个快照是来自文档查询 的初始活动文档——这应该与排序查询无关。

我找不到你的代码的问题,因为你达到了你的 firebase 配额。对我来说,您的排序似乎有点太复杂并且引入了赛车问题。我认为你可以通过使用 RxJS 来降低它的复杂性。这样的事情会起作用:https://stackblitz.com/edit/angular-ivy-cwthvf?file=src/app/app.component.ts

我无法真正测试它,因为你达到了配额。

我在一个简化的环境中使用了您的样本数据,我想我明白发生了什么。 Here is my playground,只需更新 AppModule 以指向您的 Firebase 项目。

发生了什么事?

当您致电 onSort() 时,您正在重新订阅 queryRef.onSnapshot。此回调触发两次:

  1. 订阅后立即触发,并在 Firestore 的本地缓存中触发一组可用的初始数据(在您的情况下它有一个记录,我稍后会解释)。
  2. 获取实际数据后,它会第二次触发您所希望的完整数据集。

In the playground, make sure Document is subscribed, then subscribe to the Sorted Collection. In the console, you'll see both times it fires:

不幸的是,由于您将此侦听器包装在 Promise 中,因此您只会得到第一次触发,因此只会得到缓存中的单个记录。

为什么取消订阅文档快照可以解决问题?

本地缓存似乎是根据当前打开的 onSnapshot 的订阅内容填充的。所以在你的情况下,你有一个监听器来监听单个记录的快照,因此这是你缓存中唯一的东西。

In the playground, try hitting Log Cache with various subscription states. For instance, while only Document Subscribed is true, the cache only has the one record. While both subscriptions are active, you'll see around 20 records in the cache.

If you unsubscribe from everything and hit Log Cache, the cache is empty.

事实证明,如果在上面的第 1 步中缓存中没有数据 return,它会完全跳过该步骤

In the playground, make sure Document is UNsubscribed, then subscribe to the Sorted Collection. In the console, you'll see it fired only once:

这恰好与您的 Promise 一起工作,因为第一次也是唯一一次触发包含您希望的数据。

如何解决?

This comment in the firebase-js-sdk repo 描述了预期的这种行为,以及原因。

.onSnapshot 配对的 Promise 是造成数据流混乱的主要因素,因为 .onSnapshot 预计会触发不止一次。您可以改用 .get,它在技术上是可行的。

然而,正如 Robin 指出的那样,专注于利用 @angular/fire functionality) 的更具反应性的 RxJS 方法将大有帮助。

Fully-functional example.