0

I have been doing research for the past few hours on local and global variables. What I found from this question "How do I change the value of a global variable inside of a function", and a few others was that if the variable was stated before the function (outside of it) then when you want to update the variable in a function you just say the "variable = blank" without "var" proceeding it.

If you look at the function below I have three alerts, all alerting the username of the currently logged in user via firebase. The first alert, inside the firebase reference, displays the username no problem. The other two alert "undefined". I need to be able to reference the variable in all three locations, is there something I did wrong? or is there another way to do this that works and is maybe more efficient?

var database = firebase.database();
var username;

function initApp() {
  // Listen for auth state changes.
  // [START authstatelistener]
  firebase.auth().onAuthStateChanged(function(user) {
    if (user) {
      // User is signed in.
      var displayName = user.displayName;
      var email = user.email;
      var emailVerified = user.emailVerified;
      var photoURL = user.photoURL;
      var isAnonymous = user.isAnonymous;
      var uid = user.uid;
      var providerData = user.providerData;

      var ref = firebase.database().ref("users/" + uid);
      ref.once("value").then(function(snapshot) {
        username = snapshot.child("username").val();
        alert(username);
      });
      alert(username);
    }
  });
}
alert(username);

window.onload = function() {
  initApp();
};

I understand this may seem similar to other questions, but those answers didn't seem to work despite numerous different approaches. Thanks in advance for all of your help, I am hoping this is just a stupid mistake.

Binary111
  • 149
  • 4
  • 15

1 Answers1

1

I think you are confused about the asynchronous nature of your code. Your username is only going to be available in the callback here:

ref.once("value").then(function(snapshot) {
  username = snapshot.child("username").val();
  alert(username);
});

because ref.once("value") is asynchronous (the alert immediately following the code block above will always be called before your .then callback executes). Any code that depends on username is going to have to ensure that the asynchronous call is complete before executing.

There are a number of ways to accomplish this, such as: async/await, returning a Promise from your initApp function, or changing initApp to accept a callback that runs your dependent code once the async stuff is complete. Here is an example that returns a Promise:

function initApp() {
  return new Promise(function(resolve, reject) {
    // Listen for auth state changes.
    // [START authstatelistener]
    firebase.auth().onAuthStateChanged(function(user) {
      if (user) {
        // User is signed in.
        var displayName = user.displayName;
        var email = user.email;
        var emailVerified = user.emailVerified;
        var photoURL = user.photoURL;
        var isAnonymous = user.isAnonymous;
        var uid = user.uid;
        var providerData = user.providerData;

        var ref = firebase.database().ref("users/" + uid);
        ref.once("value").then(function(snapshot) {
          username = snapshot.child("username").val();
          resolve(username);
        });
      } else {
        // no username, reject
        reject("User is not logged in")
      }
    });
  });
}

window.onload = function() {
  initApp().then(function(username) {
    alert(username);
    // do things that depend on user/username
  }).catch(function(error) {
    alert(error);
  });
};

Once the Promise is in place, you can use async/await to make your code feel more synchronous:

window.onload = async function() {
  try {
    const username = await initApp();
    alert(username);
  } catch(e) {
    alert(e);
  }
};

Keep in mind that you will still not be able to alert the username outside of this function - any code that relies on the async data must wait until that data is ready i.e. be handled in a then/callback/etc.

Rob M.
  • 35,491
  • 6
  • 51
  • 50
  • Thanks! Is there by any chance a way to make it not asynchronous? Like so that I can create other functions and reference the username at any time? Sorry for the noobie question. – Binary111 Jan 02 '18 at 22:05
  • No problem! No, you can't make it synchronous, firebase is a remote service that the browser has to make a request to - if it were synchronous then it would totally block the browser process (all tabs, etc.) while it waited for the request to complete. You can make it "feel" synchronous by using [`async/await`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function), I'll update my answer with how that might work, keep in mind that async/await won't work on older browsers though. – Rob M. Jan 02 '18 at 22:11
  • It is for a chrome extension so hopefully, it all works fine, thank you so much for your help! I asked a similar question earlier and it was marked as duplicate and sent me to a page about asynchronous and synchronous things. I had no idea what they meant by that so I was, of course, more confused. I really appreciate the detailed explanation, at least now I understand it so i can't thank you enough! – Binary111 Jan 02 '18 at 22:19
  • Glad to help! :-) I highly recommend this video about Javascript's event loop - https://www.youtube.com/watch?v=8aGhZQkoFbQ - it is a fantastic (and fun) explanation of async code in JS. Good luck! – Rob M. Jan 02 '18 at 22:22