Firebase Cloud Functions - Cannot read property 'forEach' of undefined - node.js

I have something like this:
const promises = [];
l = niz.length;
for (i = 0; i < l; i++) {
if(niz[i].length === 0) {
continue;
}
promises.push(admin.messaging().sendToDevice(niz[i], payload, options));
}
return Promise.all(promises).then((response) => {
return cleanupTokens(response, tokens);
//return resolve();
}).then(() => {
return resolve();
});
but always I have crash in firebase logs:
TypeError: Cannot read property 'forEach' of undefined
at cleanupTokens (/user_code/index.js:193:20)
at Promise.all.then (/user_code/index.js:169:20)
at process._tickDomainCallback (internal/process/next_tick.js:135:7)
Response is:
response: [ { results: [ [Object] ],
canonicalRegistrationTokenCount: 0,
failureCount: 0,
successCount: 1,
multicastId: 5591935326806715000 } ]
response: undefined
cleanup is:
function cleanupTokens(response, tokens) {
// For each notification we check if there was an error.
const tokensToRemove = {};
console.log('response: ', response);
console.log('response: ', JSON.stringify(response.results));
response.results.forEach((result, index) => {
const error = result.error;
if (error) {
console.error('Failure sending notification to', tokens[index], error);
// Cleanup the tokens who are not registered anymore.
if (error.code === 'messaging/invalid-registration-token' ||
error.code === 'messaging/registration-token-not-registered' ||
error.code === 'messaging/invalid-recipient') {
tokensToRemove[`/tokens/${tokens[index]}/g`] = null;
tokensToRemove[`/tokens/${tokens[index]}/l/0`] = null;
tokensToRemove[`/tokens/${tokens[index]}/l/1`] = null;
}
}
});
return admin.database().ref().update(tokensToRemove);
}
Any help with cleanup tokens?

You chose to send each notification separately to each user, making the response become "wrapped" as a singleton of response, and each response is also a singleton of itself - making it weird to access (response[0].results.foreach should probably work)
But even then, you'd have only 1 item to go trough, because you made each notification to be sent individually. I believe it is much more efficient to have a 1 time sendToDevice() request to all of the relevant tokens, instead of sending them separately.
Consider changing your first code to this:
//const promises = [];
var tokens = [];
l = niz.length;
for (i = 0; i < l; i++) {
//if(niz[i].length === 0) {
if(niz[i].length > 0) //I guess those are your relevant tokens
tokens.push(niz[i]); //Fill in the relevant tokens
}
//promises.push(admin.messaging().sendToDevice(niz[i], payload, options));
return admin.messaging().sendToDevice(tokens, payload, options)
.then(response => {
return cleanupTokens(response, tokens);
})
.then(() => {
return resolve();
});
//return Promise.all(promises).then((response) => {
// return cleanupTokens(response, tokens);
// //return resolve();
//}).then(() => {
// return resolve();
//});
The rest of your code should work after this change.
(Few credits to #Frank for his comment - the JSON log helped determining the issue)
EDIT:
Regarding your comment, if each niz[i] item is an array itself, you could do this:
const promises = [];
l = niz.length;
for (i = 0; i < l; i++) {
if(niz[i].length === 0) {
continue;
//promises.push(admin.messaging().sendToDevice(niz[i], payload, options));
var newPromise = return admin.messaging().sendToDevice(niz[i], payload, options)
.then(response => {
return cleanupTokens(response, tokens);
})
.then(() => {
return resolve();
});
promises.push(newPromise);
}
return Promise.all(promises);
//return Promise.all(promises).then((response) => {
// return cleanupTokens(response, tokens);
// //return resolve();
//}).then(() => {
// return resolve();
//});

Related

Promise.all returning undefined in Node JS

I have a code to fetch directory names from first API. For every directory, need to get the file name from a second API. I am using something like this in my Node JS code -
async function main_function(req, res) {
const response = await fetch(...)
.then((response) => {
if (response.ok) {
return response.text();
} else {
return "";
}
})
.then((data) => {
dirs = ...some logic to extract number of directories...
const tempPromises = [];
for (i = 0; i < dirs.length; i++) {
tempPromises.push(getFilename(i));
}
console.log(tempPromises); // Prints [ Promise { <pending> } ]
Promise.all(tempPromises).then((result_new) => {
console.log(result_new); // This prints "undefined"
res.send({ status: "ok" });
});
});
}
async function getFilename(inp_a) {
const response = await fetch(...)
.then((response) => {
if (response.ok) {
return response.text();
} else {
return "";
}
})
.then((data) => {
return new Promise((resolve) => {
resolve("Temp Name");
});
});
}
What am I missing here?
Your getFilename() doesn't seem to be returning anything i.e it's returning undefined. Try returning response at the end of the function,
async function getFilename(inp_a) {
const response = ...
return response;
}
Thanks to Mat J for the comment. I was able to simplify my code and also learn when no to use chaining.
Also thanks to Shadab's answer which helped me know that async function always returns a promise and it was that default promise being returned and not the actual string. Wasn't aware of that. (I am pretty new to JS)
Here's my final code/logic which works -
async function main_function(req,res){
try{
const response = await fetch(...)
const resp = await response.text();
dirs = ...some logic to extract number of directories...
const tempPromises = [];
for (i = 0; i < dirs.length; i++) {
tempPromises.push(getFilename(i));
}
Promise.all(tempPromises).then((result_new) => {
console.log(result_new);
res.send({ status: "ok" });
});
}
catch(err){
console.log(err)
res.send({"status" : "error"})
}
}
async function getFilename(inp_a) {
const response = await fetch(...)
respText = await response.text();
return("Temp Name"); //
}

How to avoid DEADLINE_EXCEEDED parsing firestore database to send push notifications?

I'm trying to send push notifications to 10 000+ tokens stored in Firestore.
Each time, I got a DEADLINE_EXCEEDED error.
I have already updated the timeout to 300s.
I don't know how to improve my code to avoid this error.
const pagination = 200;
function parseAndSend(push) {
const parse = async(request, total) => {
let snapshot = await request.get();
let lastVisible = snapshot.docs[snapshot.docs.length-1];
let length = snapshot.size;
var users= {};
snapshot.forEach((doc) => {
pushHelperFunctions.addUsers(doc, users);
});
pushHelperFunctions.sendMessage(users, push);
if(length < pagination) {
return console.log("finish", total+length);
} else {
let next = admin.firestore().collection("users").startAfter(lastVisible).limit(pagination);
return parse(next, total+length);
}
}
return parse(admin.firestore().collection("users").limit(pagination), 0);
}
async function sendMessage(users, push) {
Object.keys(users).forEach(key => {
if (users[key].length > 0) {
let message = payloads.setMessage(users[key], key, push);
// Send notifications to all tokens.
admin.messaging().sendMulticast(message).then(response => {
console.log(response.successCount + " messages were sent successfully");
cleanupTokens(response, users[key]);
return true;
}).catch(error => {
mailer.sendMail("messaging().sendToDevice - Error sending message "+push);
console.log("Error sending message:", error);
return false;
});
}
});
}
// Cleans up the tokens that are no longer valid.
function cleanupTokens(response, tokens) {
// For each notification we check if there was an error.
if (response.failureCount > 0) {
const tokensDelete = [];
const failedTokens = [];
response.responses.forEach((resp, index) => {
if (!resp.success) {
failedTokens.push(tokens[index]);
const deleteTask = admin.firestore().collection('users').doc(tokens[index]).delete();
tokensDelete.push(deleteTask);
}
});
console.log('List of tokens that caused failures: ' + failedTokens);
return Promise.all(tokensDelete);
} else {
return null;
}
}
Also I don't find a way to test my code without sending a prod message to my users... It makes testing difficult.

Why on my NodeJS+Express REST API a promise calling my function fails while the same promise with setTimeout works?

I have a NodeJS+Express REST API method executing reverse geocoding (using Google's Maps API).
I'm trying to solve it with Promises but the 'then' is getting executed before my function returns with the answers from Google.
When testing the same code just calling a setTimeout, it works as expected. Please see comments in the code (simplify version).
app.get('/api/v1/events', verifyToken, async (req, res) => {
await db.poolPromise.then(pool => {
return pool.request()
.input('UserId', db.sql.UniqueIdentifier, res.authData.userId)
.input('DateFrom', db.sql.DateTime2(7), req.query.dateFrom)
.input('DateTill', db.sql.DateTime2(7), req.query.dateTo)
.output('UserIdAuthorized', db.sql.Bit)
.execute('sp')
}).then(result => {
let output = (result.output || {})
if (!output.UserIdAuthorized) {
res.sendStatus(403)
}
else if (result.recordset.length > 0) {
(new Promise( (resolve) => {
//resolve(123) // this one works as expected
//setTimeout(resolve, 3000, 'temp success') // this one works as expected
// *** this one get passed and the following then is being executed before it answers ***
resolve( getAddress_TEST(result.recordset) )
// **************************************************************************************
})).then(function (value) {
res.json(
{
meta: { count: 10 }, //this is just a sample
result: value // *** this one fails with undefined ***
})
})
} else {
res.sendStatus(404)
}
}).catch(err => {
res.sendStatus(500)
console.error(err)
})
});
const nodeGeocoder_options = {
provider: 'google',
apiKey: process.env.GOOGLE_API_KEY
}
async function getAddress_TEST(recordset) {
//sample recordset for debugging - as you dont have my database
recordset = [{'eventId':14205556,'Lat':54.57767,'Lon':-2.4920483},{'eventId':14205558,'Lat':54.57767,'Lon':-2.492048},{'eventId':14205579,'Lat':53.416908,'Lon':-2.952071},{'eventId':14205588,'Lat':52.644448,'Lon':-1.153185},{'eventId':14205601,'Lat':52.29174,'Lon':-1.532283},{'eventId':14205645,'Lat':52.644448,'Lon':-1.153185},{'eventId':14205801,'Lat':53.68687,'Lon':-1.498708},{'eventId':14206041,'Lat':51.471521,'Lon':-0.2038033},{'eventId':14206049,'Lat':51.471521,'Lon':-0.2038033},{'eventId':14206072,'Lat':51.471521,'Lon':-0.2038033}]
let geocoder = nodeGeocoder(nodeGeocoder_options)
let ps = []
for (var i = 0, length = recordset.length; i < length; i++) {
if (i == 0 || !(i > 0
&& recordset[i - 1].Lat == recordset[i].Lat
&& recordset[i - 1].Lon == recordset[i].Lon)) {
ps.push(new Promise(function (resolve) {
resolve(reverseGeocode(geocoder, recordset[i].Lat, recordset[i].Lon))
}))
} else {
ps.push('-')
}
}
await Promise.all(ps)
.then(function (values) {
for (var i = 0, length = values.length; i < length; i++) {
if (values[i] != '-') {
recordset[i].locationAddress = values[i]
} else {
recordset[i].locationAddress = recordset[i - 1].locationAddress
}
}
}).then(function () {
recordset.forEach(function (v) {
delete v.Lat
delete v.Lon
});
console.log(recordset)
return recordset
})
};
async function reverseGeocode(geocoder, lat, lon) {
let address = '+'
if (lat != 0 && lon != 0) {
await geocoder.reverse({ lat: lat, lon: lon })
.then(res => {
address = res[0].formattedAddress
})
.catch(err => {
console.error(err)
});
}
return address
};
I'm sure it is something simple that I'm missing here...
The basic problem is that your getAddress_TEST function returns a promise that fulfills with nothing (undefined), because it does not contain a return statement. The return recordset is in a then() callback, from where it affects the promise resolution of the awaited promise, but that result is thrown away.
If you want to use async/await, you should get rid of any new Promise and then calls:
app.get('/api/v1/events', verifyToken, async (req, res) => {
try {
const pool = await db.poolPromise
const result = await pool.request()
.input('UserId', db.sql.UniqueIdentifier, res.authData.userId)
.input('DateFrom', db.sql.DateTime2(7), req.query.dateFrom)
.input('DateTill', db.sql.DateTime2(7), req.query.dateTo)
.output('UserIdAuthorized', db.sql.Bit)
.execute('sp')
let output = (result.output || {})
if (!output.UserIdAuthorized) {
res.sendStatus(403)
} else if (result.recordset.length > 0) {
const value = await getAddress_TEST(result.recordset)
res.json({
meta: { count: 10 }, //this is just a sample
result: value
})
} else {
res.sendStatus(404)
}
} catch(err) {
res.sendStatus(500)
console.error(err)
}
});
const nodeGeocoder_options = {
provider: 'google',
apiKey: process.env.GOOGLE_API_KEY
}
async function getAddress_TEST(recordset) {
const geocoder = nodeGeocoder(nodeGeocoder_options)
const ps = recordset.map((record, i) => {
if (i == 0 || !(i > 0
&& recordset[i - 1].Lat == record.Lat
&& recordset[i - 1].Lon == recordLon)) {
return reverseGeocode(geocoder, recordset[i].Lat, recordset[i].Lon))
} else {
return '-'
}
});
const values = await Promise.all(ps)
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
for (var i = 0, length = values.length; i < length; i++) {
if (values[i] != '-') {
recordset[i].locationAddress = values[i]
} else {
recordset[i].locationAddress = recordset[i - 1].locationAddress
}
}
recordset.forEach(function (v) {
delete v.Lat
delete v.Lon
});
console.log(recordset)
return recordset
// ^^^^^^^^^^^^^^^^
}
async function reverseGeocode(geocoder, lat, lon) {
if (lat != 0 && lon != 0) {
const res = await geocoder.reverse({ lat: lat, lon: lon })
return res[0].formattedAddress
}
return '+'
}

Return true when detect event response

I am using puppeteers. I have created a page.on('response') that is listening that is listening for requests.
I have a loop that takes care of scrolling. How can I detect if scrolling raises the 'response' event?
I was thinking of returning a boolean from the event, but how could I capture it?
page.on('response', (response) => {
if (response.url().indexOf('page') >= 0) {
return true;
} else {
return false;
}
});
while(items.length < howMuchItems) {
await page.evaluate((sel) => {
window.scrollBy(0, document.scrollingElement.querySelector(sel).scrollHeight);
}, selectors.CONTAINER_SCROLLED);
// Detect if exists event response
// If doesn´t exist => break loop
items= await page.$$(selectors.ITEM);
}
I dont know your program logic, but generally you must create like this code
const waitForNewItems = () =>
new Promise((resolve, reject) =>
page.once('response', (response) => {
if (response.url().indexOf('page') >= 0) {
return resolve(true);
} else {
return resolve(false);
}
});
while(items.length < howMuchItems) {
await page.evaluate((sel) => {
window.scrollBy(0, document.scrollingElement.querySelector(sel).scrollHeight);
}, selectors.CONTAINER_SCROLLED);
// Detect if exists event response
// If doesn´t exist => break loop
const bool = await waitForNewItems();
if (!bool) break;
items= await page.$$(selectors.ITEM);
}

How do I ensure a particular piece of code is executed after another piece of code is completely executed? With or without the help of Promises

var geoCorrectionJob = scheduler.scheduleJob('*/30 * * * * *', function(){
//Code section 1
mysqlService.getJoinedMySQLDataWithSuccess().then(function(result) {
// console.log(result) //will log results.
var response, attractionId;
for(var pos = 0; pos < result.length; pos++){
// attractionid = result[pos].attractionid
geolocationController.functionUsingPromise(result[pos]).then(function(response) { // `delay` returns a promise
// console.log(response); // Log the value once it is resolved
attractionId = response[0];
if(response[1] == 1){
attractionsDone.push(attractionId);
if(attractionsDone.length == config.scheduler.UPDATE_SIZE || pos == result.length - 1){
mysqlService.MySQL(attractionsDone).then(function(temp){
attractionsDone = [];
})
.catch((err) => {
console.log(err);
});;
}
}
else if(response[1] == 0){
mysqlService.MySql(attractionId);
}
})
.catch((err) => {
console.log(err);
});
}
})
.catch((err) => {
console.log(err);
});
// Code section 2
mysqlService.getProcessStatusOfAttractions().then(function(status){
// console.log(status);
var processed = status[0].STATUS;
var unprocessed = status[1].STATUS;
var noData = status[2].STATUS;
emailService.sendEmail(processed, noData, unprocessed);
})
.catch((err) => {
console.log(err);
});
});
How do I ensure that "Code section 2" is executed after "Code section 1" is completely executed? Code section 1 has a Promisified task within a for loop. I want Code section 2 to be executed after all the tasks in Code section 1 are completed.
var geoCorrectionJob = scheduler.scheduleJob('*/30 * * * * *', function(){
// console.log("Test Job");
var promiseArr = [];
mysqlService.getJoinedMySQLDataWithSuccess().then(function(result) {
// console.log(result) //will log results.
var response, attractionId;
for(var pos = 0; pos < result.length; pos++){
// attractionid = result[pos].attractionid
promiseArr.push(new Promise((resolve, reject) => {
geolocationController.getLatLngWithPromise(result[pos]).then(function(response) { // `delay` returns a promise
// console.log(response); // Log the value once it is resolved
attractionId = response[0];
if(response[1] == 1){
attractionsDone.push(attractionId);
if(attractionsDone.length == config.scheduler.UPDATE_SIZE || pos == result.length - 1){
mysqlService.markAttractionsDoneInMySQL(attractionsDone).then(function(temp){
attractionsDone = [];
resolve();
})
.catch((err) => {
console.log(err);
reject();
});;
}
else{
resolve();
}
}
else if(response[1] == 0){
mysqlService.markAttractionNotHavingDataInMySQL(attractionId);
resolve();
}
})
.catch((err) => {
console.log(err);
reject();
});
}));
}
Promise.all(promiseArr).then(() => {
mysqlService.getProcessStatusOfAttractions().then(function(status){
console.log(status);
var processed = status[0].STATUS;
var unprocessed = status[1].STATUS;
var noData = status[2].STATUS;
emailService.sendEmail(processed, noData, unprocessed);
})
.catch((err) => {
console.log(err);
});
});
})
.catch((err) => {
console.log(err);
});
console.log(promiseArr); // empty array
});

Resources