我在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添加新条目。只是查询。

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

最佳答案

在这里火力

您在那里发现了一个非常有趣的边缘案例。从系统的运行情况来看,您所看到的行为是预期的。但是我认为我们都可以同意这远非直觉。 :-/

本质上讲,这是一种竞赛条件,结合了Firebase对它将触发和不触发以及何时触发事件的保证。

本质上会发生以下情况:

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

这里有4个关键时刻:
  • 您为once('value')附加了/messages/message1监听器。客户端将请求发送到服务器并等待。
  • 您为on(-child_added的最后一个已知 key 附加了/messages监听器。客户端将请求发送到服务器并等待。
  • 对第一个请求的响应从服务器返回。在此阶段,有两个监听器。 once('value监听器是清除的,因此它将触发并被删除。但是在这一点上,/messages/message1也是/messages的最后一个已知键,因此客户端也将触发该child_added监听器。
  • 带有/messages/message3的响应从服务器返回。仅剩一个听众,它要求听到最后一条消息,因此它将触发。请注意,如果您还具有child_removed的监听器,则此时将是/messages/message1的监听器。

  • 如我所说,这不是很直观。但是从系统的 Angular 来看,这是正确的行为。这意味着您不希望出现这种情况,您将需要以其他方式使用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):另一个开发人员遇到此行为,并且很难维护 child 的顺序。因此,我写了一些更多的内容,说明这种行为(尽管出乎意料)仍然如何保持 child 的正确顺序。有关更多信息,请在此处查看我的答案:Firebase caching ruins order of retrieved children

    关于javascript - Firebase查询: Why is child_added called before the value query in the following code?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/47921039/

    10-10 22:22
    查看更多