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 = []
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.
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.
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With