简体   繁体   English

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

[英]Firestore active document snapshot listener breaks sorting

I am running into a firestore issue that I hope someone can help me out with.我遇到了一个firestore问题,希望有人可以帮助我。

I have an active document snapshot listener that seems to be breaking sorting behavior, and I'm not sure why.我有一个活动的文档快照侦听器,它似乎正在破坏排序行为,我不知道为什么。

In the constructor of my component, I initialize the document snapshot listener once:在我的组件的构造函数中,我初始化了一次文档快照侦听器:

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

Whenever I click the Sort button it initializes a new collection query which is sorted by first name.每当我单击“排序”按钮时,它都会初始化一个按名字排序的新集合查询。

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

  }

However, this is not working as expected.但是,这并没有按预期工作。 The sort is returning one record (there should be 20 records, based on the limit).排序返回一条记录(应该有 20 条记录,基于限制)。

在此处输入图像描述

Interestingly, unsubscribing from the document snapshot, fixes the sorting.有趣的是,取消订阅文档快照会修复排序问题。 20 records are being returned and the sorting is behaving correctly.正在返回 20 条记录,并且排序行为正确。

在此处输入图像描述

Can someone please explain what I am doing wrong?有人可以解释我做错了什么吗?

Here is a minimal Stackblitz that reproduces the issue.这是重现该问题的最小 Stackblitz。

Demo of Issue 问题演示

[Edit] [编辑]

Getting closer to a solution and an understanding... Thanks to @ZackReam for the explanation and both @Robin and @Zack for the the canonical answers.越来越接近解决方案和理解......感谢@ZackReam 的解释以及@Robin 和@Zack 的规范答案。

It seems that this._firestore.firestore.collection('users').orderBy('firstName', this.sort as any).limit(20).onSnapshot(...) is indeed emitting twice - once for the active document listener (which has 1 record in the cache), and a second time for the sorted collection (which is not in the cache since switchMap will unsubscribe everytime that the sort is executed).似乎this._firestore.firestore.collection('users').orderBy('firstName', this.sort as any).limit(20).onSnapshot(...)确实发出了两次 - 一次用于活动文档侦听器(在缓存中有 1 条记录),第二次用于排序的集合(不在缓存中,因为每次执行sortswitchMap都会取消订阅)。

We can see that briefly in the functional demo that Zack posted - it flashes with one record corresponding to the document listener, then an instant later, the list is populated with the sorted collection.我们可以在 Zack 发布的功能演示中看到这一点——它闪烁着与文档侦听器相对应的一条记录,然后片刻之后,列表中填充了排序后的集合。

The more active listeners you have, the weirder the flashes of results will be.你的听众越活跃,结果的闪现就越奇怪。 Is this by design?这是设计使然吗? seems flawed to me...对我来说似乎有缺陷......

Intuitively, I would expect snapshotChanges() or valueChanges() from the this._firestore.firestore.collection('users').orderBy('firstName', this.sort as any).limit(20) query to return 20 results (the first 20 when sorted), independent of any other document listeners.直观地说,我希望this._firestore.firestore.collection('users').orderBy('firstName', this.sort as any).limit(20)查询中的snapshotChanges()valueChanges()返回 20 个结果(排序后的前 20 个),独立于任何其他文档侦听器。 It's counter-intuitive that the first snapshot from this query is the initial active document from the document query - which should have nothing to do with the sort query.这个查询的第一个快照是来自文档查询的初始活动文档,这与直觉相反——这应该与排序查询无关。

I played around with your sample data in a simplified environment, and I think I see what's happening.我在一个简化的环境中使用了您的示例数据,我想我知道发生了什么。 Here is my playground , just update AppModule to point at your Firebase project. 这是我的游乐场,只需更新AppModule以指向您的 Firebase 项目。

What's happening?发生了什么?

When you call onSort() , you are making a fresh subscription to queryRef.onSnapshot .当您调用onSort()时,您正在重新订阅queryRef.onSnapshot This callback is firing twice:此回调触发两次:

  1. Immediately on subscribe, it fires with an initial set of data available within Firestore's local cache (in your case it has one record, I'll explain later).订阅后,它会立即触发 Firestore 本地缓存中可用的一组初始数据(在您的情况下,它有一个记录,我稍后会解释)。
  2. Once the actual data is fetched, it fires a second time with the full set of data you were hoping for.获取实际数据后,它会使用您希望的完整数据集再次触发。

In the playground, make sure Document is subscribed, then subscribe to the Sorted Collection .在 Playground 中,确保已订阅Document ,然后订阅Sorted Collection In the console, you'll see both times it fires:在控制台中,您会看到它两次触发:

在此处输入图像描述

Unfortunately, since you are wrapping this listener in a Promise , you are only ever getting the first fire, and thus only getting the single record that was in the cache.不幸的是,由于您将此侦听器包装在 Promise中,因此您只会获得第一次火灾,因此只能获得缓存中的单个记录。

Why does unsubscribing from the document snapshot fix it?为什么取消订阅文档快照会修复它?

It seems that the local cache is populated based on what subscriptions to onSnapshot are currently open.似乎本地缓存是根据当前打开的onSnapshot订阅填充的。 So in your case, you have one listener to snapshots of a single record, thus that's the only thing in your cache.因此,在您的情况下,您有一个侦听器来监听单个记录的快照,因此这是您的缓存中唯一的东西。

In the playground, try hitting Log Cache with various subscription states.在 Playground 中,尝试使用各种订阅状态访问Log Cache For instance, while only Document Subscribed is true, the cache only has the one record.例如,虽然只有Document Subscribed为 true,但缓存中只有一条记录。 While both subscriptions are active, you'll see around 20 records in the cache.当两个订阅都处于活动状态时,您会在缓存中看到大约 20 条记录。

If you unsubscribe from everything and hit Log Cache , the cache is empty.如果您取消订阅所有内容并点击Log Cache ,则缓存为空。

It turns out that if there is no data to return from the cache in step 1 above, it skips that step entirely .事实证明,如果在上面的步骤 1 中没有数据可以从缓存中返回,它会完全跳过该步骤

In the playground, make sure Document is UNsubscribed, then subscribe to the Sorted Collection .在操场上,确保Document已取消订阅,然后订阅Sorted Collection In the console, you'll see it fired only once:在控制台中,您会看到它只触发一次:

在此处输入图像描述

This happens to actually work with your Promise, since the first and only firing contains the data you were hoping for.这恰好适用于您的 Promise,因为第一次也是唯一一次触发包含您希望的数据。

How to solve this?如何解决这个问题?

This comment in the firebase-js-sdk repo describes this behavior as expected, and the reasoning why. firebase-js-sdk库中的此评论按预期描述了此行为,以及原因。

The Promise paired with .onSnapshot is the major thing that's messing up the data flow, since .onSnapshot is expected to fire more than once. Promise 与.onSnapshot配对是扰乱数据流的主要因素,因为.onSnapshot预计会触发不止一次。 You could switch to .get instead and it would technically work.你可以改用.get ,它在技术上可以工作。

However, as Robin pointed out, focusing on a more reactive RxJS approach taking advantage of the @angular/fire functionality ) would go a long way.然而,正如 Robin 指出的那样,专注于利用@angular/fire功能的更具反应性的 RxJS 方法)将 go 走很长的路。

Fully-functional example. 功能齐全的示例。

I can't find the problem with your code, because you reached your firebase quota.我找不到您的代码的问题,因为您达到了 firebase 配额。 To me, it seems your sorting is a bit too complex and introduces racing problems.对我来说,您的排序似乎有点过于复杂并引入了赛车问题。 I think you can make it way less complex by using RxJS.我认为您可以通过使用 RxJS 来降低复杂性。 Something like this would work: https://stackblitz.com/edit/angular-ivy-cwthvf?file=src/app/app.component.ts像这样的东西会起作用: https://stackblitz.com/edit/angular-ivy-cwthvf?file=src/app/app.component.ts

I couldn't really test it because you reached your quota.我无法真正测试它,因为您达到了配额。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM