问题
I have a schema in Firebase that looks like this:
messages/
$groupId/
$messageId/
message: 'Sample Message'
createdBy: 'userID'
createdAt: 1513337977055
Then I have the following queries executed consecutively in my code:
// 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()))
I'm wondering why child_added gets called twice here, the first one being similar to the value returned by the once('value') query.
Here's what the console displays:
child_added { message: 'Hello', createdAt: 1513337977055, createdBy: 'userId' }
value { message: 'Hello', createdAt: 1513337977055, createdBy: 'userId' }
child_added { message: 'Another message', createdAt: 1513337977066, createdBy: 'userId2' }
Note that I'm not adding new entries to Firebase here. Just querying.
EDIT: Here is a fiddle link demonstrating the issue: https://jsfiddle.net/dspLwvc3/2/
回答1:
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:
- You attach a
once('value')listener for/messages/message1. The client sends the request to the server and waits. You attach a
on(-child_addedlistener for the last-known key of/messages. The client sends the request to the server and waits.The response to the first request comes back from the server. At this stage there are two listeners. The
once('valuelistener is clear, so it fires and is removed. But at this point/messages/message1is also the last-known key of/messages, so the client fires thatchild_addedlistener too.- The response with
/messages/message3comes 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 forchild_removed, it would fore for/messages/message1at 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
来源:https://stackoverflow.com/questions/47921039/firebase-query-why-is-child-added-called-before-the-value-query-in-the-followin