8

With my limited understanding, and SQL background, I'm not quite groking the usage of hasChild() and forEach(); they feel like they belong on the reference and not the snapshot.

(I'll use hasChild() for the rest of the discussion, but the concepts are interchangeable.)

Here's my reasoning:

  1. I have a Firebase path called for a users table, say appname/users
  2. I want to see if a user (Fred) exists
  3. I obtain a reference: var users = new Firebase('appname/users')

But I can't determine if a child exists from here. So here is what I have now:

users.child('Fred').once('value', function(snapshot) {
   /** waits around forever if Fred doesn't exist */
});

But that doesn't quite work. So now I must get the value of users (which feels a bit counter-intuitive, since I'm not interested in users) with something like this:

var users = new Firebase('http://.../appname/users');
users.once('value', function(snapshot) { 
      snapshot.childExists('Fred', function(exists) { 
         /* do something here*/ 
      }); 
   });

I don't imagine I incur a large overhead by fetching appname/users, based on the docs, but this feels like an unsightly bit of code if I just want to determine if the key 'Fred' exists.

I'd like to see something like:

var users = new Firebase('http://.../appname/users');
users.hasChild('Fred', function(exists[, snapshotOfFred]) { 
   /* do something here*/ 
});

Is there a better approach to using forEach/hasChild? Have I missed any important logical considerations here?

Kato
  • 40,352
  • 6
  • 119
  • 149
  • The problem is even more convoluted when I want to test whether the `users` reference exists (i.e. in my test units) – Kato Jul 10 '12 at 19:41

1 Answers1

12

You were actually on the right track with your first code snippet. .once() will trigger your callback with a snapshot, regardless of whether Fred already exists or not. So you can just do:

users.child('Fred').once('value', function(snapshot) {
   if (snapshot.val() === null) {
       /* There is no user 'Fred'! */
   } else {
       /* User 'Fred' exists.
   }
});

Your second code snippet would actually incur a large overhead, since Firebase would fetch the entire 'users' location from Firebase, which could be quite large if you have 1000s of users! So the first code snippet is definitely the way to go.

The reason that hasChild and forEach are on the DataSnapshot instead of on Firebase is so that they can be synchronous. In our early API testing, we had a mix of synchronous and asynchronous methods available on the Firebase reference, but we found that this was a significant stumbling block for folks (if people see a hasChild method, they'll expect it to return a boolean immediately). So we created .on() and .once() to be the one and only asynchronous callback methods on Firebase. Everything else on a Firebase reference is synchronous (though we sometimes provide optional asynchronous completion callbacks), and everything on a DataSnapshot is 100% synchronous.

So our goal was to make the asynchronous nature of Firebase easier for people to understand and use! But perhaps we haven't succeeded 100%. :-) We'll take your feedback into account when planning future API tweaks. Thanks!

Michael Lehenbauer
  • 16,229
  • 1
  • 57
  • 59
  • Michael, thanks for clarifying about the overhead; great insight. I suppose I must have run into a bug as I tried once() on my first attempt and it never executed my callback (for records that don't exist). I'll troubleshoot and use the support email if it turns out not to be my fault. – Kato Jul 10 '12 at 21:22
  • On a side note, the reason I assumed low latency was because of this note in the [Reading Data](http://www.firebase.com/docs/reading-data.html) docs: "Attaching an event callback to a Firebase location is a very lightweight operation. You should not be concerned if your application attaches a large number (thousands) of callbacks." (there's no real clarification on what 'value' returns, so I assumed it didn't incur overhead unless I called snapshot.val(), which obviously had to do some serious loading; I hadn't thought through the fact that hasChild() is synchronous!) – Kato Jul 10 '12 at 21:31
  • Is it possible to make this kind of call in a synchronized way? – Viktor Sehr Jan 15 '14 at 16:47
  • Not exactly. You could do something like "var cachedSnapshot = null; ref.on('value', function(s) { cachedSnapshot = s; })" and then elsewhere in your code call cachedSnapshot.val() [or .forEach(), hasChild(), etc.], meaning you have synchronous access to the cachedSnapshot. But this typically isn't the best way to write your app, and you'll need to be careful to wait until cachedSnapshot is non-null (i.e. the 'value' callback has executed) before calling methods on it. – Michael Lehenbauer Jan 15 '14 at 23:59