【问题标题】:Firebase query: Why is child_added called before the value query in the following code?Firebase 查询:为什么在以下代码中的值查询之前调用 child_added?
【发布时间】:2018-01-08 02:25:35
【问题描述】:

我在 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/

【问题讨论】:

  • 这个事件会为每个现有的孩子触发一次,然后每次有新的孩子添加到指定的路径时再次触发。 ...这意味着您现在有两个数据并触发两次。如果你有 3 个,那么每个会触发 3 个。检查链接以获取更多信息。 firebase.google.com/docs/database/web/…
  • 没错。但问题是我没有添加任何新条目。我只是在查询。那么即使我设置了limitToLast(1),为什么在第一次加载时 child_added 会触发两次?
  • 这不是主题。问题是,首先触发每个项目并对其进行控制台。然后,如果您添加一条记录,则再触发一条。请参阅上面的文档。
  • 我希望 child_added 在首次加载时仅触发一次。
  • @FrankvanPuffelen 没有添加新数据。我创建了一个小提琴来演示这个问题:jsfiddle.net/dspLwvc3/2

标签: javascript firebase firebase-realtime-database


【解决方案1】:

火力基地在这里

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

它本质上是一种竞争条件,结合 Firebase 对它会触发什么、不会触发什么以及何时触发事件的保证。

基本上发生的事情是这样的:

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

其中有 4 个关键时刻:

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

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

  4. 带有/messages/message3 的响应从服务器返回。只剩下一个侦听器,它请求收听最后一条消息,因此它触发了。请注意,如果您还拥有一个child_removed 的侦听器,那么此时它将优先用于/messages/message1

正如我所说,它不是很直观。但从系统的角度来看,这是正确的行为。这意味着您不希望这种行为,您将需要以不同的方式使用 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

【讨论】:

  • 哦,哇,我只能想象 Firebase 的新手会对这种行为感到困惑。在大型 Firebase 应用程序中,这可能会出现问题,因为可能会同时调用多个查询。感谢您的明确解释!
  • 我真的相信 Firebase 文档中应该有一个关于这个特定问题的旁注
  • 在 2020 年 6 月遇到此问题。我们在 React Native Firebase 库 (github.com/invertase/react-native-firebase/issues/3940) 中遇到了此问题,此解决方案是否表明此文档因此不正确。似乎根本没有提供任何保证? firebase.google.com/docs/database/admin/…
  • 查看我的链接答案,了解如何使用事件来构建一致的内部状态。
猜你喜欢
  • 2020-09-25
  • 1970-01-01
  • 1970-01-01
  • 2017-04-04
  • 2021-12-24
  • 2014-04-19
  • 2018-12-26
  • 1970-01-01
  • 2020-04-14
相关资源
最近更新 更多