eosca
eosca

Reputation: 53

Firebase Cloud Function problem - promises related

I have a problem with a callable firebase cloud function (exports.listAllUsers).

Backend: Nodejs & Firebase-Cloud_functions to use admin.auth().listUsers
Problem: Result (usersList; an array with the uid of the users) is OK in cloud-functions-emulator (log) but NOT in the client (console.log(usersList) in browser is null)
Maybe a problem related with...: bad understanding of promises. A second code example is working with async/await but not working with .then().

The code of the function listAllUsers is basically copy&paste from docs (original code snipet: https://firebase.google.com/docs/auth/admin/manage-users#list_all_users). My code modifications are 5 (comments in the code), to get a list of users uid:

    exports.listAllUsers = functions.https.onCall(() => { // -1) callable function
      const usersList = ['12323211'] // ----------------------2) first user uid, just a sample
      function listAllUsers (nextPageToken) {
        // List batch of users, 1000 at a time.
        admin.auth().listUsers(1000, nextPageToken)
          .then((listUsersResult) => {
            listUsersResult.users.forEach((userRecord) => {
              usersList.push(userRecord.uid) // --------------3) get users uids
            })
            if (listUsersResult.pageToken) {
              // List next batch of users.
              listAllUsers(listUsersResult.pageToken)
            }
            console.log(usersList) //-------------------------4) list users uid (cloud functions console)
            return usersList //-------------------------------5) return to the client the same as showed at the console
          })
          .catch((error) => {
            console.log('Error listing users:', error)
            return null
          })
      }
      // Start listing users from the beginning, 1000 at a time.
      listAllUsers()
    })

The method in the client is...

getUsersList: async function (userType) {
      const usersList = await this.$fb.functions().httpsCallable('listAllUsers')()
      console.log('usersList: ', usersList)
    }

I am using firebase emulators. Cloud functions log is OK, you can see the sample uid and the other uids:

cloud function emulator console output

But I don't get the same in the client:

client console output

I think I am doing something wrong related with promises... because a simplification of the code is working with async/await:

    exports.listAllUsers = functions.https.onCall(async () => {
      try {
        listUsersResult = await admin.auth().listUsers()
        return listUsersResult
      } catch (error) {
        console.log('Error listing users:', error)
        return null
      }
    })

Browser console output (reduced code with async/await)

But the same is not working with then()...

    exports.listAllUsers = functions.https.onCall(() => {
      admin.auth().listUsers()
        .then((listUsersResult) => {
          return listUsersResult
        })
        .catch((error) => {
          console.log('Error listing users:', error)
          return null
        })
    })

Browser console output (reduced code with .then())

I can refactor the initial snipet of code with async/await, but I am interested in the solution with the original code (.then() flavor; I always use async/await because I am quite new at js)... Anyone can help me? Thanks!

Upvotes: 0

Views: 267

Answers (2)

eosca
eosca

Reputation: 53

Finally I decided to code an async/await version for my cloud function. The then version in the first code snipet requires more than just adding the return to the entire promises chain (it initially complains because of the recursivity, maybe, asking me to add asyncto the wrapper function listAllUsers... I'd like the then version to just copy&paste from the firebase docs, but it wanted more).

I'd like to share this homemade (but initially tested) version as a sample with async/await without recursivity to list users with admin.auth().listUsers(maxResults?, pageToken?):

// get list of users
exports.listAllUsers = functions.https.onCall(async () => {
  const usersList = []
  try {
    let listUsersResult
    let nextPageToken
    do {
      if (listUsersResult) {
        nextPageToken = listUsersResult.pageToken
      }
      // eslint-disable-next-line no-await-in-loop
      listUsersResult = await admin.auth().listUsers(1000, nextPageToken)
      listUsersResult.users.forEach((userRecord) => {
        usersList.push(userRecord.uid)
      })
    } while (listUsersResult.pageToken)
    return usersList
  } catch (error) {
    console.log('Error listing users:', error)
    return null
  }
})

Upvotes: 1

Renaud Tarnec
Renaud Tarnec

Reputation: 83191

This is because with the async/await version you correctly return listUsersResult by doing

    listUsersResult = await admin.auth().listUsers()
    return listUsersResult

but, with the then version, you don't. You should return the entire promises chain, as follows:

exports.listAllUsers = functions.https.onCall(() => {
  return admin.auth().listUsers()   // !!! Note the return here  !!!
    .then((listUsersResult) => {
      return listUsersResult
    })
    .catch((error) => {
      console.log('Error listing users:', error)
      return null
    })
})

Upvotes: 1

Related Questions