1

I'm fairly new to JavaScript, and I'm finding the concept of promises and "then" statements really hard to pin down. I'm working on a website using Firebase, and I'm trying to get user data from the database. Here's what I have at the moment:

// Function that fetches a user's data from the database.
getUserData = function(uid) {
  var database = firebase.firestore();
  var docRef = database.collection('users').doc(uid);

  var userData = docRef.get().then(function(doc) {
    if (doc.exists) {
      return doc.data();
    } else {
      console.log("User does not exist.");
      return null;
    }
  }).catch(function(error) {
    console.log("Error getting document:", error);
  });

  return userData;
}

Currently, this function returns a Promise object. When I log the object to the console, it says that the promise status is resolved and its value is the value of doc.data(). What I really want is to pass the value of doc.data() back up the "chain" to the getUserData function so that I can return and use it elsewhere. Is there a simple way to do this? I've tried to find a way to get the "value" of a Promise out of the object itself, but I've found nothing, and so far I have failed to find any other way to do what I want.

I'm sure it's a lot simpler than it seems, and I feel like an idiot for asking this sort of thing, but I've been searching for a couple of hours and none of the explanations I've found are helping. I would greatly appreciate any advice. Thanks!

Edit: Solved! Thanks for the help, everyone! Just in case this helps someone in the future who stumbles upon it, I'm leaving my final (working) code here. Unfortunately there's no way to call it from a synchronous function, but it ended up being good enough for my purposes:

// Function that fetches a user's data from the database.
async function getUserData(uid) {
  var database = firebase.firestore();
  var docRef = database.collection('users').doc(uid);

  var doc = await docRef.get();
  if (doc.exists) {
    return doc.data();
  } else {
    console.log("User does not exist.");
    return null;
  }
}
  • 1
    No, at the time of returning the promise is not yet resolved - the values are acquired asynchronously. And there's no way to get them immediately (without a time machine). You need to return the promise, and use promises all the way through. – Bergi Jul 29 '19 at 12:02
  • @Bergi I think he meant getting the result directly without having to use `.then` – jonatjano Jul 29 '19 at 12:04
  • @jonatjano Yes, and I am saying that this is not possible. – Bergi Jul 29 '19 at 12:05
  • 1
    @Bergi there are async/await specifically for this – jonatjano Jul 29 '19 at 12:06
  • @jonatjano `await` does use `then` as well. And an `async` function still returns a promise. It doesn't make anything synchronous, it's just syntactic sugar for `then` chains. – Bergi Jul 29 '19 at 12:07
  • "What I really want is to pass"..."to the getUserData function" is this even async then? Just get it an return it in the function? I am a bit less than clear on your question in that regard here – Mark Schultheiss Jul 29 '19 at 12:25
  • @MarkSchultheiss Yes, `docRef.get()` is asynchronous, and that is why you *cannot* pass it back up the chain to `getUserData` to `return` it from there. – Bergi Jul 29 '19 at 12:27
  • Perhaps just return the promise in the function and use it? `getUserData(uid).then((udata) => {...` – Mark Schultheiss Jul 29 '19 at 12:34
  • @Bergi Thanks for your help. So, is there no way to ever pass the result of an asynchronous function to a synchronous one? I don't want to make *everything* asynchronous. Is there a way to make it "wait" until the promise is resolved, then pass the value back to something else? Edit: If it clears anything up, the end goal here is to put one of the values contained within doc.data()- namely, the user's display name- onto a webpage using innerHTML. I have that part entirely written up, and if I pass it a dummy value, it displays how I want it to. – sophiegarrett Jul 29 '19 at 12:42
  • @sgarrett Yes, there's no way to block on a promise. And yes, everything that calls something asynchronous will itself become asynchronous, you cannot avoid that. This [doesn't mean that *every* function becomes async though](https://stackoverflow.com/a/45448272/1048572). – Bergi Jul 29 '19 at 12:46
  • @Bergi Thanks again for the help. Using a combination of the answers here, I have successfully managed to push the problem to another part of the program. Unfortunately, it is no closer to being solved. All I've ended up with is a short chain of asynchronous functions, and now I've hit a point where I need a value from them in a function that I cannot conceivably make asynchronous. The only workaround I can think of is to move like two lines of that function into yet another asynchronous function while leaving the rest where it is, but that feels pretty ugly. – sophiegarrett Jul 29 '19 at 13:12
  • @Bergi I figured it out! Turns out I could make that final function asynchronous; I just had to fiddle with it a bit. The final result works beautifully. Thank you (and everyone else) again for the explanations and assistance! – sophiegarrett Jul 29 '19 at 13:34

3 Answers3

1

First you must understand that Promise are mainly used for asynchronous operation, and they do not store the value UNTIL the asynchronous operation is resolved. Promises to not expose any "get" method to retrieve directly their value but only a then method to execute a callback once the promise is resolved. Thus, with Promise you always need to "wait" for it to have resolved to read the value.

Now, you have two choices :

The first is to use the old way of then() and catch().

You make your function return a Promise (like you did) and where you call your function, you can use then to execute a callback with your result.

getUserData("anId").then((userData) => console.log('the async result is' + userData))

You can also use catch to handle the error :

getUserData("anId")
.then((userData) => console.log('the async result is' + userData))
.catch((error) => console.error('wooopsie : ' + error))

As for the "new" way of doing things, you can use the async and await keywords of JS to handle your Promises.

Basically, a function declared async will always return a Promise and will define a context allowing to use the await keyword. And await will allow you to write your Promise handling just like synchronous code.

async function(){
const userData = await getUserData("anId");
console.log('the async result is' + userData);

Finally, if you need error handling, you will need to wrap the await instruction with a try-catch.

Hope this helps.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Abel
  • 688
  • 6
  • 19
  • 1
    "*they do not store the value in themself at any point*" - actually yes, they do, when the promise is fulfilled. That's why you can call `.then()` multiple times on the same promise. – Bergi Jul 29 '19 at 12:19
  • 1
    Just to make it crystal-clear: changing from direct Promises to async-await makes for what some think is a more intuitive syntax, but does not change the fundamental asynchronous nature of the underlying calls. There is no magic which turns asynchronous processing into synchronous processing. – Scott Sauyet Jul 29 '19 at 12:32
  • Thanks for the explanation! This makes a lot more sense than the other things I was able to find. I ended up using async/await, and with a bit of fiddling (along with making a few previously-written functions asynchronous) I got it to work. – sophiegarrett Jul 29 '19 at 13:39
  • I disagree, in the original code sample, the returned value _userData_ is a Promise without any async in the function declaration. It is because then() and catch() methods return the Promise with eventually some transformation. – Abel Jul 30 '19 at 15:38
1

The reason for using promises is that they make asynchronous code easier to work with. Unfortunately the code can still be a little tricky if you're not used to it. You're probably better off using async/await in most situations as it will be easier to work with and also probably easier to understand:

async function getUserData(uid) {
  var database = firebase.firestore();
  var docRef = database.collection('users').doc(uid);
  var doc = await docRef.get();
  if(doc.exists)
    return doc.data();
  return null;
}

This is how you would use it:

// code to execute before
getUserData(uid).then(userData => {
  if(userData) {
  // code to execute after
  }
  else console.log("User does not exist.");
}).catch(error => {
  console.log("Error getting document:", error);
});

Alternatively (Async/Await):

async function doSomething(uid) {
  // code to execute before
  const userData = await getUserData(uid);
  // code to execute after
}

Async/Await with custom error handling:

async function doSomething(uid) {
  // code to execute before
  try {
    const userData = await getUserData(uid);
    if(userData) {
    // code to execute after
    }
    else console.log("User does not exist.");
  }
  catch(error) {
    console.log("Error:", error);
  }
}
Jonathan Gray
  • 2,509
  • 15
  • 20
0

To get value from promise you need to use .then, so in your case to get data in the line you call getUserData, you need to use same construct (getUserData.then((data) => {}))

VitoMakarevich
  • 439
  • 2
  • 5