Firestore 活动文档快照侦听器中断排序 [英] Firestore active document snapshot listener breaks sorting

查看:28
本文介绍了Firestore 活动文档快照侦听器中断排序的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我遇到了 firestore 问题,希望有人能帮我解决.

我有一个活动文档快照侦听器,它似乎破坏了排序行为,但我不知道为什么.

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

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

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

 async onSort() {如果(this.first){this.first();}this.sort = this.sort === 'asc'?'desc' : 'asc';警报(这个.排序)让 arr = []让 lastDoc = null让 queryRef = this.fs.firestore.collection('users').orderBy('firstName', this.sort as any).limit(20);this.users$ = this._users$.asObservable()//初始化一个空列表this._users$.next([])//订阅用户列表快照更改const users1: { users: any, last: any } = await new Promise((resolve, reject) => {this.first = queryRef.onSnapshot((doc) => {console.log("当前数据:", doc);const 用户 = doc.docs.map(t => {返回 {编号:t.id,...t.data() 任何}})控制台日志(用户)解决({用户:用户,最后:doc.docs[doc.docs.length -1]})});})this._users$.next(this._users$.value.concat(users1.users))}

然而,这并没有按预期工作.排序返回一条记录(根据限制,应该有 20 条记录).

有趣的是,取消订阅文档快照,修复了排序.正在返回 20 条记录,并且排序行为正确.

有人可以解释一下我做错了什么吗?

这是重现该问题的最小 Stackblitz.

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

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

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

<块引用>

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

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

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

<块引用>

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

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

如何解决这个问题?

此注释在 firebase-js-sdk repo 按预期描述了这种行为,以及原因.

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

然而,正如 Robin 所指出的,专注于利用 @angular/fire 功能) 会大有帮助.

完全-功能示例.

I am running into a firestore issue that I hope someone can help me out with.

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

Interestingly, unsubscribing from the document snapshot, fixes the sorting. 20 records are being returned and the sorting is behaving correctly.

Can someone please explain what I am doing wrong?

Here is a minimal Stackblitz that reproduces the issue.

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.

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

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.

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

What's happening?

When you call onSort(), you are making a fresh subscription to 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).
  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. 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.

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

It turns out that if there is no data to return from the cache in step 1 above, it skips that step entirely.

In the playground, make sure Document is UNsubscribed, then subscribe to the 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.

How to solve this?

This comment in the firebase-js-sdk repo describes this behavior as expected, and the reasoning why.

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. You could switch to .get instead and it would technically work.

However, as Robin pointed out, focusing on a more reactive RxJS approach taking advantage of the @angular/fire functionality) would go a long way.

Fully-functional example.

这篇关于Firestore 活动文档快照侦听器中断排序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆