【问题标题】:Firestore active document snapshot listener breaks sortingFirestore 活动文档快照侦听器中断排序
【发布时间】:2021-06-07 17:55:53
【问题描述】:

我遇到了 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))

  }

但是,这并没有按预期工作。排序返回一条记录(应该有 20 条记录,基于限制)。

有趣的是,取消订阅文档快照会修复排序问题。正在返回 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 发布的功能演示中简单地看到 - 它闪烁着与文档侦听器相对应的一条记录,然后片刻之后,列表中填充了排序后的集合。

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

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

【问题讨论】:

    标签: angular google-cloud-firestore angularfire angularfire5


    【解决方案1】:

    我在一个简化的环境中处理了您的示例数据,我想我知道发生了什么。 Here is my playground,只需更新 AppModule 以指向您的 Firebase 项目。

    发生了什么?

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

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

    在 Playground 中,确保订阅了 Document,然后订阅了 Sorted Collection。在控制台中,您会看到两次触发:

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

    为什么取消订阅文档快照会修复它?

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

    在 Playground 中,尝试使用各种订阅状态点击 Log Cache。 例如,虽然只有Document Subscribed 为真,但缓存中只有一条记录。 当两个订阅都处于活动状态时,您会在缓存中看到大约 20 条记录。

    如果您取消订阅所有内容并点击日志缓存,则缓存为空。

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

    在 Playground 中,确保 Document 已取消订阅,然后订阅 Sorted Collection。在控制台中,您只会看到它被触发一次:

    这恰好适用于您的 Promise,因为第一次也是唯一一次触发包含您希望的数据。

    如何解决?

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

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

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

    Fully-functional example.

    【讨论】:

    • 非常感谢!我很欣赏详细的解释。我会看看你的游乐场和演示
    • 所以...this._firestore.firestore.collection('users').orderBy('firstName', this.sort as any).limit(20).onSnapshot(...),对于缓存中的每个事物都会发出两次。这对我来说没有逻辑意义。该查询特定于排序的用户。为什么发出两次有用 - 一次用于文档侦听器(有一条记录),第二次用于排序查询(20 条记录)。为什么它不会用 21 条记录发出一次?如果您无法区分其对应的查询/文档,我不确定onSnapshot 是否有用。
    • Here is an issue 在 firebase-js-sdk 中按预期描述了此行为。引用:“在您的情况下, 是本地最匹配 limit() 查询的文档 - 至少一开始是这样。后端然后告诉我们有一个不同的文档实际上排序更高,我们用服务器的结果。”
    • 因此,它并没有真正发出“一次用于文档侦听器”,然后是“第二次用于排序查询”。它发出一次用于针对本地缓存运行查询(用于立即查看数据),然后第二次用于针对后端的查询(因为后端查询返回了新的数据视图)。
    • 我的解释是,realtime update APIs 的核心是必须连续读取。无论数据来自何处,Firestore 都会在任何给定时刻为您提供尽可能多的信息。因此,永远不要将此 API 的响应视为“最终”,因为没有没有“最终”......总有更新!只需保持订阅并让数据流向您需要的地方,您将最终拥有所有数据(并继续对未来的变化做出反应)。
    【解决方案2】:

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

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

    【讨论】:

    • 明天将释放配额
    • 谢谢你的演示,我会仔细看看
    • 这是如何正确排序的好例子。当排序标准发生变化时,switchMap 将使从侦听器取消订阅变得很方便。当有一个活动文档侦听器时,为什么排序查询会从缓存中发出活动文档,您是否有任何见解?
    • 我认为是获取初始数据。等待数据第一次实际更改时会很不方便。
    猜你喜欢
    • 2021-04-07
    • 2021-03-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-15
    • 2020-01-05
    • 2020-02-24
    • 1970-01-01
    相关资源
    最近更新 更多