简体   繁体   中英

Using data returned from Firebase fetch Javascript

Web dev novice here. I'm using Firebase as my backend, and I have a situation where I need to query the DB for one value and use that returned value to match in another query to the same DB.

Here is my code:

function dataLoad() {

    var valueThatINeedToUseElsewhere

    userReference.on('value',function(users){
        var currentUser = firebase.auth().currentUser.uid
        users.forEach(function(user){
            if(user.key === currentUser) {
                valueThatINeedToUseElsewhere = user.val().userName
                console.log(valueThatINeedToUseElsewhere)
            }
        })
    })
    console.log(valueThatINeedToUseElsewhere)
}

The console.log inside the IF condition logs the correct value. However, the last console.log above does not log anything, despite the variable name being declared outside the 'on' function. Why is this happening? And how can I actually use the data outside the 'on' function? I need to use it to perform a similar IF condition in another data retrieval operation.

Thanks!

You're likely trying to do something like this now:

var valueThatINeedToUseElsewhere;
dataLoad();
doSomethingWith(valueThatINeedToUseElsewhere);

Unfortunately this doesn't work, since (as Doug says) the data is loaded from Firebase asynchronously. By the time you're trying to use valueThatINeedToUseElsewhere , the value hasn't been loaded yet.

Why this doesn't work, the one-three-two test

It's easiest to see this if you place a few log statements like this:

console.log("Before starting to load value")
userReference.on('value',function(users){
    console.log("Loaded value")
})
console.log("After starting to load value")

When you run this code, it prints:

Before starting to load value

After starting to load value

Loaded value

That is probably not the order that you expected. But it perfectly explains why you can't use valueThatINeedToUseElsewhere when you try: it hasn't been loaded yet.

Reframing the problem

The best way I've found for dealing with this situation is to reframe the problem. Your current code is written with the logic of "first we load the value, then we do something with the value". For asynchronous loading it's better to frame it as "first start loading the data. once the data has started loading, do something with it".

In code that look like:

function dataLoad() {
    userReference.once('value',function(users){
        var currentUser = firebase.auth().currentUser.uid
        users.forEach(function(user){
            if(user.key === currentUser) {
                var valueThatINeedToUseElsewhere = user.val().userName
                doSomethingWith(valueThatINeedToUseElsewhere)
            }
        })
    })
}

As you'll see, we've moved the code that needs the data into the callback that fires when the data is available. That way you can be guaranteed the data is available when you call doSomethingWith . I also changed the code to use once , which (as Doug said) is better for your use-case.

There are two more steps to take, both to improve the flexibility and performance of your code.

Passing in a function that is called once the data is loaded

First off: with this last update, the call to doSomethingWith is hard-coded, which means that dataLoad() and doSomethingWith are closely tied together. It's often better to keep them more separate. To do that, we can pass in a so-called callback function into dataLoad that it then calls when it has loaded the data. This is very similar to what once() does already. Let's see that in practice:

function dataLoad(callback) {
    userReference.once('value',function(users){
        var currentUser = firebase.auth().currentUser.uid
        users.forEach(function(user){
            if(user.key === currentUser) {
                var valueThatINeedToUseElsewhere = user.val().userName
                callback(valueThatINeedToUseElsewhere)
            }
        })
    })
}

Not too different. But now you can invoke dataLoad like this:

dataLoad(doSomethingWith);

And with that doSomethingWith will be called with the data, once the data has been loaded.

Loading the data by its key

The final change is an optimization. Your current code is loading all users, while you only need one and know their key. It's much more efficient to only load that specific user, with:

function dataLoad(callback) {
    var currentUser = firebase.auth().currentUser.uid
    userReference.child(currentUser).once('value',function(users){
        var valueThatINeedToUseElsewhere = user.val().userName
        callback(valueThatINeedToUseElsewhere)
    })
}

on() is asychronous and returns immediately, which means your console log is going to show an undefined value. The callback you pass to on() is only going to run when results are available, and there's no guarantee how quickly that will happen. You should only use a value from an asynchronous call after it finishes - don't try to make your code block until some async call is finished.

Also consider using once() instead of on() if you only need the value at a location a single time. You can use its returned promise to chain some additional work after it loads the data you want. It is also asynchronous (as is all other methods that return a promise).

To learn more about why Firebase APIs are asynchronous, and what that means for your app, read this blog .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM