Firebase查询:为什么在以下代码中的值查询之前调用了child_added?

时间:2017-12-21 08:37:50

标签: javascript firebase firebase-realtime-database

我在Firebase中有一个如下所示的架构:

messages/
  $groupId/
    $messageId/
      message: 'Sample Message'
      createdBy: 'userID'
      createdAt: 1513337977055

然后我在我的代码中连续执行了以下查询:

// Get a specific message
ref.child('messages/$groupId/$messageId')
  .once('value')
  .then(snap => console.log('value', snap.val()))

// Start new message listener
ref.child('messages/$groupId')
  .orderByKey()
  .limitToLast(1)
  .on('child_added', snap => console.log('child_added', snap.val()))

我想知道为什么child_added在这里被调用两次,第一个类似于once('value')查询返回的值。

以下是控制台显示的内容:

child_added { message: 'Hello', createdAt: 1513337977055, createdBy: 'userId' }
value { message: 'Hello', createdAt: 1513337977055, createdBy: 'userId' }
child_added { message: 'Another message', createdAt: 1513337977066, createdBy: 'userId2' }

请注意,我不是在这里向Firebase添加新条目。只是查询。

编辑:这是一个演示问题的小提琴链接:https://jsfiddle.net/dspLwvc3/2/

1 个答案:

答案 0 :(得分:5)

firebase here

你在那里发现了一个非常有趣的边缘案例。您所看到的行为是系统运行的预期。但我认为我们都同意它远非直观。 : - /

这基本上是一种竞争条件,加上Firebase对它将会发生什么,不发射以及发生事件的保证。

基本上会发生什么:

    Client                  Server
      |                       |
   (1)|  --once('value'---->  |
      |                       |
   (2)|  -on('child_added'->  |
      |                       |
      |           .           |
      |           .           |
      |           .           |
      |                       |
      |         value         |
   (3)|  <------------------- |
      |                       |
      |         child         |
   (4)|  <------------------- |
      |                       |

那里有4个关键时刻:

  1. 您为once('value')附加了/messages/message1听众。客户端将请求发送到服务器并等待。
  2. 您为on(-child_added的最后一个已知密钥附加了/messages侦听器。客户端将请求发送到服务器并等待。

  3. 对第一个请求的响应从服务器返回。在这个阶段有两个听众。 once('value监听器是清晰的,因此它会触发并被删除。但此时/messages/message1也是/messages的最后一个已知密钥,因此客户端也会触发child_added侦听器。

  4. /messages/message3的响应从服务器返回。只剩下一个听众,它要求听到最后一条消息,所以它会触发。请注意,如果您还有一个child_removed的监听器,那么此时它将适用于/messages/message1
  5. 正如我所说,它不是很直观。但从系统的角度来看,这是正确的行为。这意味着您不希望出现此行为,您将需要以不同的方式使用API​​。使用当前代码的最简单的方法是将child_added侦听器附加到 once('value'回调中:

    ref.child('messages/$groupId/$messageId')
      .once('value')
      .then(snap => {
        console.log('value', snap.val()))
    
      // Start new message listener
      ref.child('messages/$groupId')
        .orderByKey()
        .limitToLast(1)
        .on('child_added', snap => console.log('child_added', snap.val()))
      })
    

    这是有效的,因为在连接child_added侦听器时,/messages/message1快照已经从客户端的缓存中刷新。

    更新(2018-01-07):另一位开发人员遇到此行为,并且很难维护子级的顺序。所以我写了一些关于这种行为(虽然意外)仍然保持孩子的正确顺序的更多信息。有关详情,请参阅我的回答:Firebase caching ruins order of retrieved children