firestore onSnapshot 执行两次

Posted

技术标签:

【中文标题】firestore onSnapshot 执行两次【英文标题】:firestore onSnapshot executing twice 【发布时间】:2018-10-02 23:21:30 【问题描述】:

我的 firestore onSnapshot() 函数被调用了两次。

let user = firebase.firestore().collection('users').doc(userID).onSnapshot
(                    
    next: (documentSnapshot: firebase.firestore.DocumentSnapshot) =>
    
         this.userArray.push(documentSnapshot as User);
         console.log(documentSnapshot);
         //here
    ,
    error: (firestoreError: firebase.firestore.FirestoreError) =>
    
         console.log(firestoreError);
         //here
    
);

我也尝试过像https://firebase.google.com/docs/firestore/query-data/listen#detach_a_listener 一样通过在 //here 评论中包含 user() 来订阅,但无济于事。

如何修改使函数只执行一次,即每次只推送一个用户对象而不是两次。

【问题讨论】:

【参考方案1】:

如果您需要一次性响应,请使用 .get() 方法进行承诺。

firebase.firestore().collection('users').doc(userID).get().then(snap => 
  this.userArray = [...this.userArray, snap.doc);
);

但是,我建议使用AngularFire(完全有偏见,因为我维护了库)。它使处理常见的 Angular + Firebase 任务变得更加容易。

【讨论】:

这不是违背了使用onSnapshot的目的吗?据我了解,get() 不会监听变化。【参考方案2】:

我不知道这是否与您的问题有关。如果有人使用

firebase.firestore.FieldValue.serverTimestamp()

给文档一个时间戳,然后 onSnaphot 会触发两次。这似乎是因为当您向数据库添加新文档时,onSnapshot 会触发,但 serverTimestamp 尚未运行。几毫秒后,serverTimestamp 将运行并更新您的文档 => onSnapshot 将再次触发。

我想在 onSnapshot 触发之前添加一个小延迟(比如 0.5 秒左右),但我找不到这样做的方法。

您也可以为 onCreate 事件创建一个服务器端函数,我相信这会解决您的问题。也许您的 userArray.push-action 更适合在服务器端执行。


更新:要详细了解serverTimestamp() 的行为以及它触发两次侦听器的原因,请阅读这篇文章:The secrets of Firestore’s FieldValue.serverTimestamp() — REVEALED!。此外,官方文档指出:

当您执行写入时,您的侦听器将收到新数据的通知在数据发送到后端之前

在文章中有几个建议的解决方案,其中之一是使用snapshotmetadata 属性来查找metadata.hasPendingWrites 的布尔值是否为真(它告诉您快照您'正在查看尚未写入服务器)或false。

例如,在您的情况下,您可以检查hasPendingWrites 是否为假,然后推送对象:

if ( !documentSnapshot.metadata.hasPendingWrites )
  // This code will only execute once the data has been written to the server 
  this.userArray.push(documentSnapshot as User);
  console.log(documentSnapshot);

在更通用的示例中,代码如下所示:

firestore.collection("MyCollection")
.onSnapshot( snapshot => 

    if ( snapshot.metadata.hasPendingWrites )
        // Local changes have not yet been written to the backend
     else 
        // Changes have been written to the backend
    

);

在文档中找到的另一种有用的方法如下:

如果你只想知道你的写入何时完成,你可以收听完成回调而不是使用hasPendingWrites。在 javascript 中,通过附加 .then() 回调来使用写入操作返回的 Promise。

我希望这些资源和各种方法能够帮助任何试图找出解决方案的人。

参考:

Events for local changes The hasPendingWrites metadata property Snapshot Listen Options

【讨论】:

就我而言,当从客户端设置FieldValue.serverTimestamp() 时,第一个通知包含相关字段的空值;第二个通知包含服务器时间戳值。我最终使用云功能来执行相同的逻辑,它为我解决了双重触发问题。 在我的情况下,这是一个new Date() 用法,这很可能。内部触发firebase.firestore.FieldValue.serverTimestamp()调用 这里也一样。 metadata.fromCache的值变化也可以触发更新

以上是关于firestore onSnapshot 执行两次的主要内容,如果未能解决你的问题,请参考以下文章

哪个反应钩子与firestore onsnapshot一起使用?

如何通过 redux-observable/rxjs 使用 Firestore 实时更新(onSnapshot)?

Cloud Firestore 中 get() 和 onSnapshot() 的区别

将firestore onSnapShot与react redux一起使用的正确方法

firestore onSnapshot 更新值,但不更新 VueJS 中的 DOM

React:如何通过 Firestore onSnapshot 和 useEffect 使用最新状态?