1

I am new to JavaScript and have been trying to read up a lot on why this is not working. Here is my code. I have also read a number of articles here on stack overflow but still feeling dense

Also if my title does not make sense, please suggest an edit

 listRef.listAll()
            .then(response => {
                let files = []  
                response.items.forEach(item => {
                    
                  var text
                  getText(item.name).then(res=>{text = res});
                    
                    const id = {uid: guid()}
                    const url = item.getDownloadURL().then(url => {return url} )
                    const gsurl = `gs://archivewebsite.appspot.com/${folder}/${item.name}`
                    files.push({...item, name:item.name, url, gsurl, id:id.uid, text})
                
                    });
                    this.files = files;
                  })
            .catch(error => console.log(error));


  async function getText(docID) {
        var docRef = firestore.collection("recipes").doc(docID);
        let doc = await docRef.get()
        if (doc.exists){
           return doc.data().text
         }
}

that code "works" in that it logs the response to the console but the text variable is a pending promise object.

I understand that async functions return a promise so when I call getText I need to use .then - what I am struggling with and have refactored this code a few times is this: how can I assign the value of doc.data().text to a variable to be used later in other words, how can var text be an actual string and not a promise object pending

Also for my own learning on javascript inside the async function if I replace

if (doc.exists){
           return doc.data().text
         }

with

 if (doc.exists){
           return Promise.resolve(doc.data().text)
         }

I get the same result in console.log - is this expected? is return simply short hand for the handler to resolve the promise?

I have also refactored this code to be non async and I get the same result where my var text is basically a pending promise and never the resolved data

Thanks for your help - also any articles to help explain this to me would be great! I have been going through courses on udemy but little confused by this right now

Kevin
  • 199
  • 5
  • 20

3 Answers3

3

Actually you are assigning the complete promise to the variable text

Replace

var text = getText(item.name).then(res=>console.log(res))

by

var text = await getText(item.name);

OR

var text
getText(item.name).then(res=>{text = res});
Nav Kumar V
  • 320
  • 2
  • 10
  • Hey thank you so much for the response! I am understanding that I can only use the await inside of an async function - so i am not sure if this will work for me also so sorry I think I see my issue but still not sure why I edited my original post with the full code - so when I call getText its inside another .then method – Kevin Aug 20 '20 at 02:53
  • I'm trying something similar as the last example but I would like the assignment of the return value to be `const`. But that's not possible, is there some other way to do that? If I try like the first example with either `let` och `const` I get `Promise {: undefined}` The `console.log` is resolved but not the assignment. – Oortone Nov 19 '21 at 12:37
  • @Oortone can you try using async-await ```const text = await getText(item.name);``` – Nav Kumar V Nov 24 '21 at 07:14
  • @NavKumarV: I get: `"Uncaught SyntaxError: await is only valid in async functions and the top level bodies of modules"`. Maybe if I reconstruct the whole code and use modules and/or encapsulation, I'm not sure. – Oortone Nov 26 '21 at 10:07
  • @Oortone I guess u are using `await` without the `function being declared as async`. Please go through https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function – Nav Kumar V Nov 29 '21 at 04:16
  • @NavKumarV Actually I'm in the global scope. The idea was to read initial conditions (that should not change) from a file. But I probably need to redesign the whole idea if I want it to be `const`. I'm quite new to Javascript. – Oortone Nov 30 '21 at 20:09
  • @Oortone Sorry, but I am not getting a clear picture about your requirements. Actually it will be better if you can post your code(a modified sample code) and tag me, So it will be easy to sort things out – Nav Kumar V Dec 01 '21 at 02:46
  • @NavKumarV Thanks for helping out, but I'm in the processing of redesigning this so it would be a waste of time for both of us at this point. – Oortone Dec 16 '21 at 15:13
2

OK I worked with someone at work and found a solution - it was related to this post https://stackoverflow.com/a/37576787/5991792 I was using async function inside a for each loop

the refactored code is here

async function buildFiles(){
  let items = await listRef.listAll()
  let files = []
  for (const item of item.items){
    const text = await getText(item.name)
    const url = await item.getDownloadURL()
    const gsurl = `gs://archivewebsite.appspot.com/${folder}/${sermon.name}`
    files.push({...item, name:item.name, url, gsurl, text})  
}
return files
}


 async function getText(docID) {
        var docRef = firestore.collection("recipies").doc(docID);
        let doc = await docRef.get()
        if (doc.exists){return await doc.data().text}}



buildFiles().then(res=>this.files = res)

Thanks also to @cyqsimon and @Nav Kumar V

Kevin
  • 199
  • 5
  • 20
1

Of course text is going to be a Promise. Promise.then() always returns a Promise.

Consider this code:

function doA(n) {
  // do something here...
  console.log("A" + n);
}
asnyc function doB(n) {
  // do something here...
  console.log("B" + n);
}

doA(1);
doA(2);
doB(3); // async
doA(4);
doB(5); // async
doB(6); // async
doA(7);

What do you expect the output to be?

  • 1, 2, 4, and 7 will always be in order, because they are executed synchronously.
  • 3 will not ever print before 1 and 2. Likewise, 5 and 6 will not ever print before 1, 2, and 4.
  • However, 3, 5, and 6 can be printed in any order, because async functions do not guarantee execution order once created.
  • Also, 4 can print before 3. Likewise, 7 can print before 5 and 6.

Basically, think of async functions as a parallel task that runs independently (although not really; single thread JS only simulates this behavior). It can return (fulfill/reject) at any moment. For this reason, you cannot just simply assign a return value of an async function to a variable using synchronous code - the value is not guaranteed to be (and probably is not) available at the moment of synchronous execution.


Therefore you need to put all the code that requires the value of text to be set into the callback block of the Promise, something like this:

getText(item.name).then((text) => {
  // put everything that uses text here
});

This can of course lead to the infamous "callback hell", where you have layers inside layers of async callback. See http://callbackhell.com for details and mitigation techniques.

async/await is just one of the newer ways to do the same thing: MDN has an excellent article here: https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await

cyqsimon
  • 2,752
  • 2
  • 17
  • 38
  • Thank you for explaining this more, I did edit my main question to include the full scope of where I am calling the async function and I think this may be part of my issue, if I call it outside of the .then on the listRef everything works fine – Kevin Aug 20 '20 at 03:08