Crashalot
Crashalot

Reputation: 34523

Firebase Cloud Firestore query returning Promise { <pending> } instead of fulfilled when using async/await

This question is similar but didn't help.

The goal is to use async/await with Firebase Cloud Firestore queries instead of the then/catch Promise code from the Firebase documentation.

However, the function below rejects successfully in the negative case, but returns Promise { <pending> } instead of the Cloud Firestore doc.

async getUser(emailAddress) {
    // Return null if @emailAddress blank.
    if (!emailAddress) {
        return null;
    }

    // Assume @db points to a Cloud Firestore database. Query only returns one doc.
    let query = db.collection('users').where('emailAddress', '==', emailAddress);
    try {
        let querySnapshot = await query.get();
        querySnapshot.forEach(function(doc) {
           return doc.data();               
        });
    } catch(e) {
        console.log('Error getting user: ', e);
    }
}

Upvotes: 2

Views: 2447

Answers (3)

Renaud Tarnec
Renaud Tarnec

Reputation: 83191

If emailAddress is not null or undefined, your function returns nothing becauseforEach() returns void.

Since you have only one doc in the querySnapshot you can do as follows:

async getUser(emailAddress) {
    // Return null if @emailAddress blank.
    if (!emailAddress) {
        return null;
    }

    // Assume @db points to a Cloud Firestore database. Query only returns one doc.
    const query = db.collection('users').where('emailAddress', '==', emailAddress);
    try {
        const querySnapshot = await query.get();
        return querySnapshot.docs[0].data();               
    } catch(e) {
        console.log('Error getting user: ', e);
    }
}

Note that your getUser() function is itself asynchronous, so you need to use then() (or async/await) to get the doc value when the Promise it returns is fulfilled.

const emailAddress = "[email protected]";
getUser(emailAddress).
then(userData => { ... })

Upvotes: 3

Yair Cohen
Yair Cohen

Reputation: 2268

You are trying to return from forEach, but forEach doesn't return anything, rather it loops through elements and allows you to modify them.

You can modify your code like this for example:

async getUser(emailAddress) {
    // Return null if @emailAddress blank.
    if (!emailAddress) {
        return null;
    }

    // Assume @db points to a Cloud Firestore database. Query only returns one doc.
    let query = db.collection('users').where('emailAddress', '==', emailAddress);
    try {
        const results = [];
        let querySnapshot = await query.get();
        querySnapshot.forEach(function(doc) {
           results.push(doc.data());               
        });
        return results;
    } catch(e) {
        console.log('Error getting user: ', e);
    }
}

More info about forEach:

forEach() executes the callback function once for each array element; unlike map() or reduce() it always returns the value undefined and is not chainable. The typical use case is to execute side effects at the end of a chain.

Upvotes: 3

Doug Stevenson
Doug Stevenson

Reputation: 317928

async functions, such as yours, always return a promise. That can't be avoided. All of the asynchronous behavior is still there in the function, and the caller still has to await or then to get the value in the promise. You can't use async/await to remove a promise and return immediately.

On top of that, your function doesn't actually return any documents. The return inside the forEach is actually just returning from the inline lambda function that you passed to forEach. That return doesn't extend to the enclosing getUser function.

If you want to use async/await effectively, the function still has to return a value from its top level. And the caller still has to use await or then to get the value out of the returned promise.

Upvotes: 2

Related Questions