1

According to the Firebase documentation:

Value events are always triggered last and are guaranteed to contain updates from any other events which occurred before that snapshot was taken.

Here is a simple example (jsbin) where child_added fires before value. This behavior was confirmed using the currently latest Firebase version (2.3.1):

var ref = new Firebase("https://reform.firebaseio.com");
ref.child('pets').once('value', function(snapshot) {

    snapshot.forEach(function(pet) {
        console.log("Pet: " + pet.key());

        pet.ref().child('food').once('value', function (foods) {
            console.log('value event called for ' + pet.key());
        });

        pet.ref().child('food').once('child_added', function (foods) {
            console.log('child_added event called for ' + pet.key());
        });
    });
});

In this example, the console log will be:

Pet: cat
Pet: dog
value event called for cat
child_added event called for cat
value event called for dog
child_added event called for dog

Why does the child_added event fire last in this case? Does this not violate the guarantee per the documentation?

  • 2
    Use `on()` instead of `once()` and the order will make more sense: http://jsbin.com/kuyida/edit?js,console – Frank van Puffelen Nov 04 '15 at 15:16
  • I confirmed that the suggested change corrects the order. Could you please elaborate on the reason why the order is correct when using the `on` function and not `once`? Does this not violate the specification, which does not appear to differentiate between the two functions? It simply states that the order of the events is guaranteed? I am only wondering in case this needs to be filed as a bug with Firebase. – Cristian Almstrand Nov 04 '15 at 17:48
  • 1
    You're attaching two separate listeners, which each fire a separate request and then immediately detach themselves. By using `once()` for these, you are sequencing the events. You can solve that by not using `once()`. – Frank van Puffelen Nov 04 '15 at 17:59
  • 1
    While the suggested change to use `on()` instead of `once()` addresses the issue with the event order described in the original post, attaching another `child_added` listener to the top level object ("pets") causes the order of the events attached to the child properties to be incorrect again. This can observed in this modified example: http://jsbin.com/selevo/edit?html,js,console. This seems to be yet another case violating the "Value events are always triggered last[...]" guarantee in the docs. Is there a way to make the order correct again, after adding the last line of code in the example? – Cristian Almstrand Nov 06 '15 at 02:29
  • 1
    Just to update this post with findings/resolution from speaking with Firebase support: The reason for the reversed event order in the ([modified example](http://jsbin.com/selevo/2/edit?html,js,console)) is that (quote) "[I was adding]... callbacks to the existing data. When the callback is being added, the client library is immediately firing the callback without waiting for the second one to be registered since all the data is available." In the example, the order can be enforced by reversing the order in which the callbacks are added: http://jsbin.com/secuqo/1/edit?html,js,console – Cristian Almstrand Nov 06 '15 at 23:21
  • Sounds like an answer. Self-answering is encouraged on StackOverflow. – Frank van Puffelen Nov 07 '15 at 01:48

1 Answers1

2

To summarize the excellent feedback in the comments and by Firebase Support:

  1. It makes most sense here to use on() instead of once() to register the event listener. In the example of the original post, and to quote Firebase Support:

    The on() callback is registered, and when the once() callback is registered it is ordered in reference to the on() callback. Once the once() callback is fired, it's automatically deregistered. Even though the execution of the events are done in a certain order (due to javascript being single threaded), they are being calculated separately from each other.

    Frank's correction to that example shows this in action.

  2. The modified example again breaks the "guarantee" because (Firebase Support):

    The data is already locally on the client. So once you have run ref.child('pets').on() and the callback happens, all the data under /pets has been retrieved to the client. Now in the callback processing, you are adding additional callbacks to the existing data. When the callback is being added, the client library is immediately firing the callback without waiting for the second one to be registered since all the data is available.

    Since I would like to enforce the guarantee in this case where the data is local, I simply register the child_added listener before the value listener, as demonstrated in the correction to the modified example.