Ivalo Pajumets Ivalo Pajumets - 1 year ago 49
Javascript Question

Reference error, says variable is not defined

So I have a function which reads from Firebase database and then creates an array of objects. I have defined variable

key
, but it is still unavailable.

var usersList = [];

const ref = firebase.database().ref()

function fetchUsers() {

ref.child('users').once('value').then(snap => {
var promises = [];

snap.forEach(childSnap => {

var key = childSnap.key

promises.push
(ref.child(`users/${key}/points`).once('value')
);


});

return Promise.all(promises);

}).then(function(snapshots) {
return snapshots.map(snapper => {
var points = snapper.val()
return {uid: key, points: points};
})
}).then(function(usersList) {
console.log(usersList)
})

}


And this is the error I get...

(node:11724) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): ReferenceError: key is not defined


If i just do:
key = childSnap.key
, then every object's
uid
is the same.

Answer Source

key is only defined within the forEach callback. Naturally you cannot then reference it from within the second then callback. Moreover, which key would it be? For the first entry? The second? Third?

Instead, you need to return the key with the value:

function fetchUsers() {
    ref.child('users').once('value').then(snap => {
        var promises = [];
        snap.forEach(childSnap => {
            var key = childSnap.key;
            promises.push(
                ref.child(`users/${key}/points`).once('value').then(
                    snapper => ({key, snapper})    // **
                )
            );
        });

        return Promise.all(promises);
    }).then(function(snapshots) {
        return snapshots.map(({key, snapper}) => { // **
            var points = snapper.val();
            return {uid: key, points: points};
        });
    }).then(function(usersList) {
        console.log(usersList);
    });
}

On the first ** line above, we're transforming the result from once so it returns an object with both the key and "snapper".

On the second ** line, we're using destructured parameters to receive those objects as the distinct key and snapper parameters.


FWIW, if you want to aggressively use concise arrows:

function fetchUsers() {
    ref.child('users').once('value').then(snap =>
        Promise.all(snap.map(childSnap => {
            const key = childSnap.key;
            return ref.child(`users/${key}/points`)
                        .once('value')
                        .then(snapper => ({key, snapper}));
        }))
    ).then(snapshots =>
        snapshots.map(({key, snapper}) => ({uid: key, points: snapper.val()}))
    ).then(usersList => {
        console.log(usersList);
    });
}

Also note the use of map rather than declaring an array and pushing to it from a forEach callback. :-)

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download