Skip to content

Instantly share code, notes, and snippets.

@bendrucker
Last active August 29, 2015 14:20
Show Gist options
  • Save bendrucker/1d2451c0933405e96a8c to your computer and use it in GitHub Desktop.
Save bendrucker/1d2451c0933405e96a8c to your computer and use it in GitHub Desktop.
Firebase caught releasing Zalgo!

Reproducing

  1. Create a reference
  2. Add a value listener
  3. Wait two seconds to ensure the data loads
  4. Add a value listener on a child

Result

In our script, childListener is run synchronously. That means it runs before we increment the count and the assertion fails. This releases the dark͝ tend̴r̡i҉ls of Zalgo.

The linked post does a decent job of explaining this, but let's get Firebase specific. If we comment out parent.on('value', function parentListener () {}), the assertion will pass. By calling listeners sometimes synchronously and sometimes asynchronously, the prior state of the program leaks unexpectedly into user code.

Google Closure provides a good nextTick implementation so this should be reasonably easy to resolve.

var parent = new Firebase('https://zalgo.firebaseio.com/')
parent.on('value', function parentListener () {})
setTimeout(function () {
var child = parent.child('foo')
var count = 0
child.on('value', function childListener () {
console.assert(count === 1) // but it's 0!
});
count++
}, 2000)
@jamestalmage
Copy link

I've had some situations where I've needed to filter updates from the server vs local updates:

var ref = new Firebase('url');

var domUpdate = false;

ref.on('value', function onFirebaseValue(snap) {
  if (domUpdate) return;
  // set Value to the Dom
});

function onDomValue(newValue) {
  domUpdate = true; //essentially disables the on('value') callback temporarily
  ref.set(newValue);
  domUpdate = false;
}

This helps eliminate a constant cycle of updates. Note this is not really a problem in most situations (calling ref.set twice with identical values won't trigger the callback twice).. As I recall my need for it was brought about by some weirdness with the GoogleMaps LatLng API, and in another situation floating point rounding errors, and in another situation where I was setting Firebase.TIMESTAMP on every update.

I see this as solvable in one of two ways:

  1. Continue to call listeners synchronously when ref.set() is called.
  2. Provide some universal way of knowing if a callback is being called because of a local ref.set() call, or by new data from the server.

Option 1 certainly provides better backwards compatibility for some of my legacy code, but I do not know that it is the best way.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment