javascriptnode.jsfirebasefirebase-realtime-databasefirebase-authentication

I cannot get data from firebase database


I am new to firebase. I'm trying to retreive data from a real time database using a node.js server sending associated credentials to firebase, but something gets broken after once('value') is called: its returned promise never gets resolved and server stops itself logging this message: "Process exited with code 3221226505".

I wrote the following code:

async function testFirebase1(firebaseCredentialsObj, path) {
  let firebase = require('firebase')
  firebase.initializeApp(firebaseCredentialsObj);
  var database = firebase.database();
  var ref = database.ref(path);
  console.log(ref.toString());
  try {

    // Attempt 1 
    var prom = await ref.once('value'); 
    const data = prom.;
    console.log('data ' + data)

    // Attempt 2
    prom.then((snapshot) => {
      console.log('snapshot ' + snapshot)
    }).catch((error) => { console.log(error)} )

  } catch (error) {
    console.log(error)
  }
}

No error ever gets catched. I also tried to get data as an admin, but i got the same failing result

async function testFirebase3(firebaseCredentials, serviceAccountKey, databaseURL, path) {
  const admin=require('firebase-admin');
  const serviceAccount = serviceAccountKey;
  admin.initializeApp({
    credential: admin.credential.cert(serviceAccount),
    databaseURL: databaseURL
    });
    var db=admin.database();
    var userRef=db.ref(path);    
    const prom = await userRef.once('value');
    console.log(prom)
}

Promise returned from once() method keep beeing pendent. This is its log:

[[PromiseStatus]]:'pending' [[PromiseValue]]:undefined

Server is supposed to get databases'data in json format and send it to the client. Why is this happening?


Solution

  • Based on your code, you are mixing traditional Promise chaining and async/await syntax together which is leading to your confusion.

    Note: In the below snippets, I use the database query coding style I describe at the end of this answer.

    SDK Initialization

    To start with, in both testFirebase1 and testFirebase3, you initialize the default Firebase app instance in the function. If you call either function only once, you won't experience any problems, but any time you call them another time, they will always throw an error stating that the app has already been initialized. To solve this, you can lazily load these libraries using the following functions:

    function lazyFirebase(options, name = undefined) {
      const firebase = require('firebase');
      // alternatively use the Promise-based version in an async function:
      // const firebase = await import('firebase');
    
      try {
        firebase.app(name);
      } catch (err) {
        firebase.initializeApp(options, name);
      }
      return firebase;
    }
    
    function lazyFirebaseAdmin(options, name = undefined) {
      const admin = require('firebase-admin');
      // alternatively use the Promise-based version in an async function:
      // const admin = await import('firebase-admin');
    
      try {
        admin.app(name);
      } catch (err) {
        const cred = options.credential;
    
        if (typeof cred === "string") {
           options.credential = admin.credential.cert(cred)
        }
    
        admin.initializeApp(options, name);
      }
      return admin;
    }
    

    Important Note: Neither of the above functions checks whether they use the same options object to initialize them. It just assumes they are the same configuration object.

    Correcting testFirebase1

    In testFirebase1, you are initializing the default Firebase app instance and then starting the process of the getting the data from the database. Because you haven't returned the promise from the ref.once('value') in the function, the caller will get a Promise<undefined> that resolves before the database call completes.

    async function testFirebase1(firebaseCredentialsObj, path) {
      let firebase = require('firebase')
    
      // bug: throws error if initializeApp called more than once
      firebase.initializeApp(firebaseCredentialsObj);
    
      // bug: using `var` - use `const` or `let`
      var database = firebase.database();
      var ref = database.ref(path);
      console.log(ref.toString());
      try {
    
        // Attempt 1 
        // bug: using `await` here, makes this a DataSnapshot not a Promise<DataSnapshot>
        //      hence `prom` should be `snapshot`
        // bug: using `var` - use `const` or `let`
        var prom = await ref.once('value');
    
        // bug: syntax error, assuming this was meant to be `prom.val()`
        const data = prom.;
        console.log('data ' + data)
    
        // Attempt 2
        // bug: a `DataSnapshot` doesn't have a `then` or `catch` method
        // bug: if `prom` was a `Promise`, you should return it here
        prom
          .then((snapshot) => {
            console.log('snapshot ' + snapshot)
          })
          .catch((error) => {
            console.log(error)
          })
    
      } catch (error) {
        console.log(error)
      }
    }
    

    Correcting these problems (and making use of my coding style when dealing with RTDB queries) gives:

    async function testFirebase1(firebaseCredentialsObj, path) {
      const firebase = lazyFirebase(firebaseCredentialsObj);
    
      const snapshot = await firebase.database()
        .ref(path)
        .once('value');
    
      // returns data at this location
      return snapshot.val();
    }
    

    Correcting testFirebase3

    In testFirebase3, you are initializing the default Firebase Admin app instance and correctly waiting for the data from the database. Because you haven't returned the data from the database, the caller will get a Promise<undefined> that resolves when the database call completes but without the containing data.

    async function testFirebase3(firebaseCredentials, serviceAccountKey, databaseURL, path) {
      const admin = require('firebase-admin');
    
      // note: unnecessary line, either call `serviceAccountKey` `serviceAccount` or use `serviceAccountKey` as-is
      const serviceAccount = serviceAccountKey;
    
      // bug: throws error if initializeApp called more than once
      // bug: `firebaseCredentials` is unused
      // note: when initializing the *default* app's configuration, you
      //       should specify all options to prevent bugs when using
      //       `admin.messaging()`, `admin.auth()`, `admin.storage()`, etc
      //       as they all share the default app instance
      admin.initializeApp({
        credential: admin.credential.cert(serviceAccount),
        databaseURL: databaseURL
      });
    
      // bug: using `var` - use `const` or `let`
      var db=admin.database();
      var userRef=db.ref(path);
    
      // bug: using `await` here, makes this a DataSnapshot not a Promise<DataSnapshot>
      //      hence `prom` should be `snapshot`
      const prom = await userRef.once('value');
                                                
      // bug: logging a `DataSnapshot` object isn't useful because it
      //      doesn't serialize properly (it doesn't define `toString()`,
      //      so it will be logged as "[object Object]")
      console.log(prom)
    }
    

    Correcting these problems (and making use of my coding style when dealing with RTDB queries) gives:

    async function testFirebase3(firebaseCredentials, serviceAccountKey, databaseURL, path) {
      const admin = lazyFirebaseAdmin({
        ...firebaseCredentials, // note: assuming `firebaseCredentials` is the complete app configuration,
        credential: serviceAccountKey,
        databaseURL: databaseURL
      });
    
      const snapshot = await admin.database()
        .ref(path)
        .once('value');
    
      return snapshot.val();
    }