BIS Tech
BIS Tech

Reputation: 19444

how to await in for loop firebase cloud function using typescript

I have a query to find users from firestore DB. Then I need to find which user is online or not. I used build presence(real-time database) for that.

In my logic, sometimes return an empty array. status is true on realTime DB. I think it will return without finish for a loop.

const users: Array<any> = [];

await asyncForEach(query.docs, async (loc: any) => {
  const data = loc.data();
  await rtDb.ref(data.uid).once('value', function (snap) {
    if (snap.val().status === true) {
      users.push(data);
    }
  }).catch((e) => {
    console.log(`${e}`)
  });
});

return users;


async function asyncForEach(array: Array<any>, callback: Function) {
  for (let index = 0; index < array.length; index++) {
    await callback(array[index], index, array);
  }
}

Upvotes: 0

Views: 211

Answers (1)

David Tennant
David Tennant

Reputation: 98

It's tough to say where the error may be. If you are getting an empty array, are you sure loc.data() is not throwing an error? Or there are other errors stopping the push from happening?

You could use a combination of promises and Promise.all. You can use a standardfor-of

Promises

You can turn the realTime DB data return into a promise. Promise docs

  const rtDbPromise = (loc: QueryDoc): Promise<UserData | null> => {
    const data = loc.data();
    return new Promise((resolve, reject) => {
      rtDb.ref(data.uid).once("value", (snap: Snap) => {
        try {
          if (snap.val().status) {
            resolve(data);
          } else {
            resolve(null);
          }
        } catch (e) {
          reject(e);
        }
      });
    });
  };

Promise all

Then you can add all your promises to an array and use Promise.all. Looping with a for-of

type UserData = { uid: string };
type Snap = { val: () => { status: boolean } };
type QueryDoc = { data: () => UserData };
type MockRtDb = {
  ref: (
    uid: string
  ) => {
    once: (type: "value", callback: (snap: Snap) => void) => Promise<void>;
  };
};

async function getOnlineUsers(): Promise<UserData[]> {
  const rtDb = {} as MockRtDb;

  // Create the async call here (Promise)
  const rtDbPromise = (loc: QueryDoc): Promise<UserData | null> => {
    const data = loc.data();
    return new Promise((resolve, reject) => {
      rtDb.ref(data.uid).once("value", (snap: Snap) => {
        try {
          if (snap.val().status) {
            resolve(data);
          } else {
            resolve(null);
          }
        } catch (e) {
          console.log(e);
          // This will throw an error. If any promises fail, Promise.all will throw as well.
          // Maybe return null if you don't want errors. Read the docs for more info
          reject(e);
        }
      });
    });
  };

  const dataArray: QueryDoc[] = [];
  const promise: Promise<UserData | null>[] = [];

  for (const data of dataArray) {
    promise.push(rtDbPromise(data));
  }

  const dataWithNulls: (UserData | null)[] = await Promise.all(promise);
  // Filter out the nulls
  return dataWithNulls.filter((item) => item !== null);
}

const onlineUsers = await getOnlineUsers();

Upvotes: 2

Related Questions