6

I'm working on FCM and need device tokens for all member in channel/room to send push notifications and every member have multiple devices, for that i need two for loop.

I'm using async/await with firestore queries but it not wait for result, process it in background and move to next statement which need result data.

const notification = async (channelId) => {
    let tokens = []
    const members = await db.collection('channels/' + channelId + '/members').get();
    await members.forEach(async (member) => {
        const deviceTokens = await db.collection('users/' + member.id + '/devices').get();
        await deviceTokens.forEach(async (token) => {
            console.log(token.id);
            await tokens.push(token.data().token);
        })
    })
    console.log(tokens);
    return await sendPush(tokens); // calling other functions
}

I expect the output is tokens = ['token1', 'token2', 'token3'], but the actual output is tokens = []

Umar
  • 261
  • 2
  • 9
  • 1
    Possible duplicate of [Using async/await with a forEach loop](https://stackoverflow.com/questions/37576685/using-async-await-with-a-foreach-loop) – Estus Flask Apr 18 '19 at 13:27
  • @estus i asked about nested forEach please have a look – Umar Apr 18 '19 at 13:34
  • 1
    I am also stuck with this problem but not find any solution. – Moaiz Apr 18 '19 at 13:40
  • 1
    @Umar It doesn't matter if it's nested or not. You shouldn't use forEach together with async/await, that's the point, this is explained in dupe question. – Estus Flask Apr 18 '19 at 14:02
  • @estus you are absolutely right but problem is that firestore returned array only works with forEach, was tried it with map, for..of.. , for..in.. but nothing work – Umar Apr 18 '19 at 14:13
  • 1
    I currently don't use Firebase and can't check if it's workable. I posted an answer. Hope this helps. – Estus Flask Apr 18 '19 at 14:37

2 Answers2

9

forEach cannot be efficiently used together with async..await. Since a query returns query snapshot, an array should be iterated instead. Promise chains can be executed in series with for..of or in parallel with Promise.all and array map, as explained in related question, e.g.:

const notification = async (channelId) => {
    let tokens = [];
    const members = await db.collection('channels/' + channelId + '/members').get();
    for (const member of members.docs) {
      const deviceTokens = await db.collection('users/' + member.id + '/devices').get();
      for (const deviceToken of deviceTokens.docs) {
        tokens.push(deviceToken.data().token);
      }
    }

    return await sendPush(tokens);
}

await sendPush(...) will work correctly only if sendPush returns a promise.

Estus Flask
  • 206,104
  • 70
  • 425
  • 565
4

I guess you missunderstood the usage of async/await.

You can use await for functions that are async. Array.forEach is not an async function so you dont need to use it for your inner loop.

I would recommend to use promises here:

    async doStuff(){
        let tokens = [];
        const members = await db.collection('channels/' + channelId + '/members').get();

        let promises = members.map(member => {
            return db.collection('users/' + member.id + '/devices').get();
        })
        // or the forEach
        let promises = [];

        members.forEach(member => {
            promises.push(db.collection('users/' + member.id + '/devices').get());
        });

        Promise
            .all(promises)
            .then(values => {
                values.map(token => {
                    console.log(token.id);
                    tokens.push(token.data().token);
                });

                sendPush(tokens);
            })
    }

Now you gather all the members then start for all members the token requests and when all() of them finished you can push your tokens in that array and send them.

BraveButter
  • 1,408
  • 9
  • 22
  • 1
    Using map instead of forEach and ignoring a value it returns is an antipattern. It already returns an array - at least it should if `members` is an array. Also this code won't result in correctly chained promises. – Estus Flask Apr 18 '19 at 14:10
  • 1
    changed that map return, idk if op wants to get a return value from that function or not – BraveButter Apr 18 '19 at 14:15
  • @BraveButter array.map not work with firestore query result – Umar Apr 18 '19 at 14:17
  • 1
    you can change that to forEach that should be no problem – BraveButter Apr 18 '19 at 14:18