Reputation: 1683
I have a firebase messaging function, but the return function seems to execute before the rest of the functions. Here is the code (sorry its long):
exports.newMessageNotification = functions.firestore
.document(`messages/{messageId}`) // wildcard for the msg id
.onCreate(async (change, context) => {
const db = admin.firestore();
const messageId: string = context.params.messageId;
const messageRef = db.collection('messages').doc(messageId);
const tokens = [];
// get the message
const message: Message = await messageRef
.get()
.then(q => q.data() as Message);
const recipients: any = await message.recipients;
const user: User = await db
.collection('users')
.doc(message.senderId)
.get()
.then(q => {
return q.data() as User;
});
// Notification content
const payload = await {
notification: {
title: `${user.name}`,
body: `${message.message}`,
},
};
console.log(payload);
// loop though each recipient, get their devices and push to tokens
Object.keys(recipients).forEach(async recipient => {
const devicesRef = db
.collection('devices')
.where('userId', '==', recipient);
const devices = await devicesRef.get();
devices.forEach(res => {
const token: string = res.data().token;
console.log(token);
tokens.push(token);
});
});
console.log(tokens); // logs empty
return await sendToCloud(tokens, payload);
});
I think the issue is that the forEach is not asynchronous so the final line of code is executing before waiting for forEach to finish?
Upvotes: 0
Views: 294
Reputation: 4277
Ugh. I had this problem somewhere recently. You are correct, at least in my experience: forEach
does not seem to obey the async
directive. I had to use the for ... in ...
syntax to:
async
(from the parent scope)In your case, it would probably look like for (const recipient in recipients) { ... }
Apparently, this is caused by forEach
calling the callback on each item without awaiting a response. Even if the callback is asynchronous, forEach
doesn't know to await
its response on each loop.
Source: https://codeburst.io/javascript-async-await-with-foreach-b6ba62bbf404
A solution from the comments on the blog linked above:
await Promise.all(
Object.keys(recipients).map(async recipient => {
const devicesRef = db
.collection('devices')
.where('userId', '==', recipient);
const devices = await devicesRef.get();
devices.forEach(res => {
const token: string = res.data().token;
console.log(token);
tokens.push(token);
});
});
)
Note the forEach
has been replaced by a map
, and it's within Promise.all(...)
.
Upvotes: 2