Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there any solution to wait for nested forEach until return result using async/await in node.js

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 = []

like image 942
Umar Avatar asked Apr 18 '19 13:04

Umar


People also ask

Does forEach wait for async?

forEach is not designed for asynchronous code. (It was not suitable for promises, and it is not suitable for async-await.) For example, the following forEach loop might not do what it appears to do: const players = await this.

Why async-await not working in forEach?

The Solution It turns out that the array. forEach method only accepts a synchronous function, and therefore is NOT compatible with the async/await syntax. Instead, you can use the for … of iterator as per below which is compatible with the async/await syntax.

Can we use async-await in for loop?

You need to place the loop in an async function, then you can use await and the loop stops the iteration until the promise we're awaiting resolves. You could also use while or do.. while or for loops too with this same structure.

Does async-await wait?

Await: Await function is used to wait for the promise. It could be used within the async block only. It makes the code wait until the promise returns a result. It only makes the async block wait.


2 Answers

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.

like image 101
Estus Flask Avatar answered Oct 18 '22 22:10

Estus Flask


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.

like image 27
BraveButter Avatar answered Oct 18 '22 22:10

BraveButter