4

I am struggling with async operations. I am trying to simply get a value from firestore and storing it in a var.

I manage to receive the value, I can even save it in the var when I do that specifically (use the var within the get function) but I don't seem to manage the await properly when trying to save this in a flexible way:

async function getValues(collectionName, docName,) {
console.log("start")
var result;
var docRef = await db.collection(collectionName).doc(docName).get()
  .then(//async// (tried this as well with async) function (doc) {
    if (doc.exists) {
      console.log("Document data:", doc.data());
      result = doc.data().text;
      console.log(result);
      return //await// (this as well with async) result;
    } else {
      // doc.data() will be undefined in this case
      console.log("No such document!");
      result = "No such document!";
      return result;
    }
    console.log("end");
  }).catch (function (err) {
    console.log('Error getting documents', err);
  });
};

helpMessage = getValues('configuration','helpMessage');

Note: doc.data().text -> "text" is the name of the field where my value is stored in. Do I have to use .value here?

The result I get in the console is:

info: Document data: { text: 'The correct text from the database' }
info: The correct text from the database

But using helpMessage in my code I get

{}

Image from the Telegram bot where I am trying to use the helpMessage as a response to the '/help' command.

I have checked: getting value from cloud firestore, Firebase Firestore get() async/await, get asynchronous value from firebase firestore reference and most importantly How do I return the response from an asynchronous call?. They either deal with multiple documents (using forEach), don't address the async nature of my problem or (last case), I simply fail to understand the nature of it.

Additionally, both nodejs and firestore seems to be developing rapidly and finding good, up-to-date documentation or examples is difficult. Any pointers are much appriciated.

hong4rc
  • 3,999
  • 4
  • 21
  • 40
RobSteward
  • 305
  • 3
  • 11

3 Answers3

15

You have things the wrong way around. It's much easier than you think it is.

function getValues(collectionName, docName) {
    return db.collection(collectionName).doc(docName).get().then(function (doc) {
        if (doc.exists) return doc.data().text;
        return Promise.reject("No such document");
    }};
}

If a function returns a promise (like db.collection(...).doc(...).get()), return that promise. This is the "outer" return above.

In the promise handler (inside the .then() callback), return a value to indicate success, or a rejected promise to indicate an error. This is the "inner" return above. Instead of returning a rejected promise, you can also throw an error if you want to.

Now you have a promise-returning function. You can use it with .then() and .catch():

getValues('configuration','helpMessage')
    .then(function (text) { console.log(text); })
    .catch(function (err) { console.log("ERROR:" err); });

or await it inside an async function in a try/catch block, if you like that better:

async function doSomething() {
    try {
        let text = await getValues('configuration','helpMessage');
        console.log(text);
    } catch {
        console.log("ERROR:" err);
    }
}

If you want to use async/await with your getValues() function, you can:

async function getValues(collectionName, docName) {
    let doc = await db.collection(collectionName).doc(docName).get();
    if (doc.exists) return doc.data().text;
    throw new Error("No such document");
}
Tomalak
  • 332,285
  • 67
  • 532
  • 628
  • 1
    Thanks Tomalak! I guess I am missing something. I changed the function as suggested and then used the first recommendation you gave (then/catch). `var helpMessage = await getValues('configuration', 'helpMessage') .then((text) => { console.log(text); return text}) .catch((err) => { return 'ERROR:' + err}); async function getValues(collectionName, docName) { let doc = await db.collection(collectionName).doc(docName).get(); if (doc.exists) return doc.data().text; throw new Error("No such document"); }; bot.help((ctx) => ctx.reply('helpMessage: ', helpMessage));` – RobSteward Mar 19 '19 at 14:31
  • Okay and what are you missing? – Tomalak Mar 19 '19 at 17:30
  • Sorry, that should have been part of my comment: The value does not store the result returned from the `return text` segement. So `helpMessage` is still empty when called in `bot.help((ctx) => ctx.reply('helpMessage: ', helpMessage));` Note, the `bot.help` function is only triggered when I type `/help` in Telegram to that bot. It does return `helpMessage: ` (with a space then nothing), so the function works and reacts, but the value in helpMessage seems to be empty. – RobSteward Mar 19 '19 at 18:35
  • Hard to say. Are you sure that `doc.data().text` even contains the text you're looking for? Could there be a typo? – Tomalak Mar 20 '19 at 07:26
3

Since getValues function returns a promise, you need to await getValues function while calling it.

Change getValues like so -

function getValues(collectionName, docName,) {
  console.log("start")
  var result;
  return db.collection(collectionName).doc(docName).get()
    .then(function (doc) {
      if (doc.exists) {
        console.log("Document data:", doc.data());
        result = doc.data().text;
        console.log(result);
        return result;
      } else {
        // doc.data() will be undefined in this case
        console.log("No such document!");
        result = "No such document!";
        return result;
      }
    }).catch (function (err) {
      console.log('Error getting documents', err);
    });
  };

Then use getValues like so -

helpMessage = await getValues('configuration','helpMessage');

Explanation -

async, await are just syntactic sugar for Promises. async functions return a promise (or AsyncFunction more accurately) which needs to be resolved to use its enclosed value.

See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function

Yasser Hussain
  • 854
  • 7
  • 21
  • Hi Yasser, tried this. But using getValues needs to be in an async function, which it is not for me. Thus this fails. var helpMessage = await getValues('configuration', 'helpMessage') ^^^^^ SyntaxError: await is only valid in async function – RobSteward Mar 19 '19 at 13:45
  • Yes, getValues must be in an async function. Can't you call getValues in async function? An alternative would be to use promise.then function. – Yasser Hussain Mar 19 '19 at 16:12
1

Finally managed to get it working. Thanks for the input Tomalak!

getValues(help.collectionName, help.docName)
  .then((text) => {
    console.log(text);
    help.message = text;
   })
  .catch((err) => { console.log("Error: ", err); });

function getValues(collectionName, docName) {
  return db.collection(collectionName).doc(docName).get().then((doc) => {
    if (doc.exists) {
      return doc.data().text;
    }
    else {
      return Promise.reject("No such document");
    }});
  }

bot.help((ctx) => ctx.reply(help.message));

Unfortunately, I can not pin-point the exact reason this worked. Some little fixes (missed comma in the console.log) and formatting definitely helped me understanding the structure though. Hope someone else finds this useful, when starting to play around with node and firebase.

RobSteward
  • 305
  • 3
  • 11