Firebase query: Why is child_added called before the value query in the following code?

前端 未结 1 749
北海茫月
北海茫月 2021-01-13 08:33

I have a schema in Firebase that looks like this:

messages/
  $groupId/
    $messageId/
      message: \'Sample Message\'
      createdBy: \'userID\'
      c         


        
相关标签:
1条回答
  • 2021-01-13 09:08

    firebase here

    You've uncovered a quite interesting edge case there. The behavior you're seeing is expected from how the system operates. But I think we can all agree that it's far from intuitive. :-/

    It's essentially a race condition, combined with Firebase's guarantees about what it will and won't fire and when it fires events.

    Essentially what happens is this:

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

    There are 4 key moments in there:

    1. You attach a once('value') listener for /messages/message1. The client sends the request to the server and waits.
    2. You attach a on(-child_added listener for the last-known key of /messages. The client sends the request to the server and waits.

    3. The response to the first request comes back from the server. At this stage there are two listeners. The once('value listener is clear, so it fires and is removed. But at this point /messages/message1 is also the last-known key of /messages, so the client fires that child_added listener too.

    4. The response with /messages/message3 comes back from the server. There is only one listener left and it requested to hear about the last message, so it fires. Note that if you'd also have a listener for child_removed, it would fore for /messages/message1 at this point.

    As I said, it's not very intuitive. But from the system's perspective it is the correct behavior. That means that you don't want this behavior, you will need to use the API in a different way. The simplest one with your current code would be to move attaching the child_added listener into the once('value' callback:

    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()))
      })
    

    This works because, by the time the child_added listener is attached, the /messages/message1 snapshot has already been flushed from the client's cache.

    Update (2018-01-07): another developer encountered this behavior and had a hard time maintaining the order of children. So I wrote up a bit more how this behavior (while unexpected) still maintains the correct order of the children. For more, see my answer here: Firebase caching ruins order of retrieved children

    0 讨论(0)
提交回复
热议问题