Problem to use a Map in Firebase Functions - node.js

I am trying to get the length of a Map and I keep getting "undefined". Could please someone tell me what am I doing wrong?
This is the part of the code that gives me problems.
const GYMdetail: { [key: string]: number} = {};
GYMdetail[`${doc.data().name} (${doc.data().personalID})`] = 650;
const subtotal = 650 * GYMdetail.size;
This is the complete function code
export const addGymMonthlyExpense =
functions.https.onRequest((request, response) => {
const query1 = admin.firestore().collection("users");
const query = query1.where("subscriptions.gym.active", "==", true);
query.get()
.then(async (allUsers) => {
allUsers.docs.forEach(async (doc) => {
if (doc != undefined) {
const houseForGym = doc.data().subscriptions.gym.house;
await admin.firestore()
.doc(`houses/${houseForGym}/expenses/2022-04`)
.get().then((snapshot) => {
if (snapshot.data() == undefined) {
console.log(`${houseForGym}-${doc.data().name}: CREAR!!`);
} else if (snapshot.data()!.issued == false) {
let detail: { [key: string]: any} = {};
const GYMdetail: { [key: string]: number} = {};
detail = snapshot.data()!.detail;
GYMdetail[
`${doc.data().name} (${doc.data().personalID})`
] = 650;
const subtotal = 650 * GYMdetail.size;
detail["GYM"] = {"total": subtotal, "detail": GYMdetail};
snapshot.ref.set({"detail": detail}, {merge: true});
}
return null;
})
.catch((error) => {
console.log(
`${houseForGym} - ${doc.data().name}: ${error}`);
response.status(500).send(error);
return null;
});
}
});
response.send("i");
})
.catch((error) => {
console.log(error);
response.status(500).send(error);
});
});

Since you are executing an asynchronous call to the database in your code, you need to return a promise from the top-level code; otherwise Cloud Functions may kill the container when the final } executes and by that time the database load won't be done yet.
So:
export const addGymMonthlyExpense =
functions.https.onRequest((request, response) => {
const query1 = admin.firestore().collection("users");
const query = query1.where("subscriptions.gym.active", "==", true);
return query.get()
...
Next you'll need to ensure that all the nested get() calls also get a chance to finish before the Functions container gets terminated. For that I recommend not using await for each nested get call, but a single Promise.all for all of them:
query.get()
.then(async (allUsers) => {
const promises = [];
allUsers.docs.forEach((doc) => {
const houseForGym = doc.data().subscriptions.gym.house;
promises.push(admin.firestore()
.doc(`houses/${houseForGym}/expenses/2022-04`)
.get().then((snapshot) => {
...
});
});
response.send("i");
return Promise.all(promises);
})
.catch((error) => {
console.log(error);
response.status(500).send(error);
});

Related

Nodejs Function is returning undefined

I have 2 files index.js and models.js
my index.js file requires the model.js file
const models = require("./modules/models.js");
app.post('/message', (req, res) => {
const _lead = models.createLead(req.body)
_lead.then(l => {
console.log("resulted in lead", JSON.stringify(l))
// GETTING UNDEFINED HERE
})
// const _message = await models.createMessage(req.body.message)
res.send();
});
My models.js file has a functions that contain Promises
const createLead = async function (payload) {
// Find lead
let result
new Promise((resolve, reject) => {
findLeadById(payload.lead.rid).then(async _lead => {
// If a lead has not been found
if (!_lead) {
if(payload.companyRid != payload.lead.rid){
await db.collection('leads').add(payload.lead).then((l) => {
result = payload.lead
result['id'] = l.id
})
}
} else {
result = _lead
}
}).then(() => {
console.log(" result of create lead ",JSON.stringify(result))
resolve(result)
}).catch(error => {
error.log("reject", error)
reject(error)
})
})
}
const findLeadById = async function (lead) {
new Promise(async (resolve, reject) => {
const ref = db.collection('leads');
console.log("finding lead", JSON.stringify(lead))
await ref.where("rid", "==", lead).get().then((s) => {
let obj = null
if(s){
s.forEach(doc => {
console.log("found the lead in loop", JSON.stringify(doc.data()))
obj = doc.data()
obj['id'] = doc.id
})
}
resolve(obj)
}).catch(error => {
error.log("reject", error)
reject(error)
})
})
}
exports.createLead = createLead;
exports.findLeadById = findLeadById;
The model.js returns an object as expected
console.log(" result of create lead ",JSON.stringify(result))
However, I'm expecting to see that same object in the index.js in the then block but I'm getting undefined.
console.log("resulted in lead", JSON.stringify(l))
// GETTING UNDEFINED HERE
The following is not waiting for the associated functions to finish
const _lead = models.createLead(req.body)
I have also tried adding async and await which also didn't work
const _lead = await models.createLead(req.body).then(res => {console.log("result", res) // Still undefined
})
I think you can use resolve for any success response, and reject for error response. Ex:
await db.collection('leads').add(payload.lead).then((l) => {
let result = payload.lead
result['id'] = l.id
resolve(result)
})

Unable to map inside an async function

In the below code, I am fetching data from an external api. After parsing the data as json, I wanted to map through it and get a modified version.
For some reason, the console.log(jsonData) inside the map function is not getting executed. Please check the code below for clarity
const getRandomOutfit = async (req, res) => {
const { gender, countryCode } = req.params;
if (req.params.gender === "FEMALE" || req.params.gender === "MALE") {
try {
const response = await fetch(URL);
const jsonData = await response.json();
const outputData = jsonData.map((productItem) => {
console.log(productItem); // doesn't get printed
// some operation
return productItem;
});
await res.json(jsonData);
} catch (error) {
res.status(500).send("Error getting data");
}
} else {
res.status(500).send("Invalid category");
}
};
I'm confused about what I am missing here and making an error.
I rewrote the code to make it clearer to understand. In general, it is best to take the fail first approach. Notice, how the first thing I do is return upon failure.
As to why you code is not printing anything out, try printing jsonData. It might be that this is an empty array.
const getRandomOutfit = async (req, res) => {
const { gender, countryCode } = req.params;
if (gender !== "FEMALE" && gender !== "MALE")
return res.status(500).send("Invalid category");
try {
const response = await fetch(URL);
const jsonData = await response.json();
console.log(jsonData); // what does this return?
const outputData = jsonData.map((productItem) => {
console.log(productItem); // doesn't get printed
// some operation
return productItem;
});
await res.json(jsonData);
} catch (error) {
res.status(500).send("Error getting data");
};

Cannot get the result of an async function in controller

This is my controller:
const rssService = require('../services/rss.service');
async function parser() {
const result = await rssService.rssParser('someurl');
return result;
};
const parse = async function (req, res) {
const p = new Promise((resolve, reject) => {
const t = parser();
if (t === undefined) {
resolve(t);
} else {
// eslint-disable-next-line prefer-promise-reject-errors
reject('something bad happened');
}
});
p.then((result) => res.send(result)).catch((message) => console.log(`ERROR ${message}`));
};
module.exports = {
parse,
};
in the function : parser() above, I am trying to call my rss.service.js file which I have the logic. This file is a rss parser which tries to parse the feed and do some calculations (which needs promises and async) and then return the json object.
Here is how my rss.service look :
const rssParser = async function parseRssFeed(url) {
const parser = new Parser();
const appRoot = process.env.PWD;
const downloadDir = `${appRoot}/downloads/`;
if (!fs.existsSync(downloadDir)) {
fs.mkdirSync(downloadDir);
}
try {
const feed = await parser.parseURL('someurl');
const processedFeedItems = await Promise.all(feed.items.map(async (currentItem) => {
const {
fileUrl,
downloadPath,
} = await downloadFile(currentItem);
const hashValue = calculateHash(downloadPath)
return {
title: currentItem.title,
hash: hashValue,
url: mp3FileUrl,
};
}));
return (JSON.stringify(processedFeedItems));
} catch (error) {
console.error(error);
return 'error';
}
};
when I debug my code I can verify that Json object has been created with correct data, but the result does not return to the callee(controller).
I'll go in a little deeper since you mentioned you're new:
const rssService = require('../services/rss.service');
// This is an async function (always returns a promise)
async function parser() {
const result = await rssService.rssParser('someurl');
return result;
};
const parse = async function (req, res, next) {
// In await/async, you should use try/catch/throw instead of .then and .catch
// It does the same thing, but it's the preferred syntax and is "easier" to read IMO
// Out in "the community" people will complain if you mix await/async with promises like that
try {
// Adding await to ensure left-assign works.
// This is necessary because parser is an async function (so returns a promise)
const result = await parser();
// here, since you used `await` you get the value instead of the promise
if (result === undefined) throw new Error('something bad happened')
return res.send(result)
} catch (error) {
console.log(`ERROR ${error.message}`;
// Do you need to do anything else with this error? I assume something like:
return next(error);
}
};
module.exports = {
parse,
};
In a fast look, It seems you have forgot to wait for resolve the parser promise.
...
const p = new Promise(async(resolve, reject) => {
const t = await parser();
...

how to refer to return value of a function from outside in Node.js

I've been making dynamic dropdown box that each option has the table's name of BigQuery and I want to use return value (list) that is made inside .then method in function listTables(). However it seems not to work well . I'm new to Js so could you give any tips ?? Thank you so much.
function listTables() {
const {BigQuery} = require('#google-cloud/bigquery');
const bigquery = new BigQuery({
projectId: 'test_project',
});
const list = [];
bigquery
.dataset("test_table")
.getTables()
.then(results => {
const tables = results[0];
tables.forEach(table => list.push(table.id));
return console.log(list);←I want to use this list outside a function
})
.catch(err => {
console.error('ERROR:', err);
});
}
listTables();
// select tag
let slt = document.getElementById("slt");
addTables(slt);
// return data
function getList() {
return new Promise(function (onFulliflled, onRejected) {
onFulliflled(list);
});
}
function addTables(slt) {
getList()
.then((list) => {
for (item of list) {
// optionを作成
let option = document.createElement("option");
option.text = item;
option.value = item;
// optionの追加
slt.appendChild(option);
}
})
.catch((err) => {
console.error("error", err);
});
}
.then(results => {
const tables = results[0];
tables.forEach(table => list.push(table.id));
return console.log(list);
})
RESULT
[ 'test', 'test1', 'test2', 'test3' ]
You can do this in multiple ways.
Using calllback
function listTables(callback) {
//...
.then(results => {
const tables = results[0];
tables.forEach(table => list.push(table.id));
callback(list);
})
//...
}
listTables(function(list){
});
Using promise or async/await
function listTables() {
return new Promise(function(resolve, reject){
//...
.then(results => {
const tables = results[0];
tables.forEach(table => list.push(table.id));
resolve(list);
})
//...
});
}
// Promise
listTables().then(function(list){
});
//Async/await
var list = await listTables();
For the await to work you also need to run in within an async function. For example wrap it in async iife.
(async function(){
var list = await listTables();
})();
I don't use await myself so this is just from top of my head and might need some changes.

Handling promises inside the forEach loop

I am trying to run a series of tasks. Each task is dynamic, and could have different rules to follow. This will be executed on AWS-Lambda.
I have an array of JSON. It has a body with task name in it, and it also has attributes.
I need to dynamically load a javascript file with the name inside the body.
I need to wait until all is finished inside that task. Or it failed (regardless where). If the fail happens, I will need to write that data inside the current record inside the forEach loop.
I have old issue, where my forEach is finished first without waiting for the task to complete.
This is the forEach loop:
const jobLoader = require('./Helpers/jobLoader');
event.Records.forEach(record => {
const { body: jobName } = record;
const { messageAttributes } = record;
const job = jobLoader.loadJob(jobName);
job.runJob(messageAttributes).then(res => {
console.log('Show results');
return; // resume another record from forEach
}).catch(err => {
record.failed = true;
record.failureMessage = err.message;
console.log('I errored');
});
console.log('All Done');
});
The problem is that message All Done is printed, and then the message show results is printed. I get results from the database once it comes for execution.
This is the file that loads a task:
exports.loadJob = (jobName) => {
const job = require(`../Tasks/${jobName}`);
return job;
};
This is the file that contains actual task:
const mySqlConnector = require('../Storage/mySql');
exports.runJob = async (params) => {
let payload = {};
let dataToSend = await getUserName(params.userId.stringValue);
payload.dataToSend = dataToSend;
let moreDataToSend = await getEvenMoreData(params.userId.stringValue);
payload.moreDataToSend = moreDataToSend;
return await sendData(payload);
};
const getUserName = async (userId) => {
const query = 'SELECT * FROM user_data';
return await mySqlConnector.handler(query);
};
const getEvenMoreData = async (userId) => {
const query = 'SELECT * FROM user_data';
return await mySqlConnector.handler(query);
};
const sendData = (payload) => {
//this should be Axios sending data
};
And this is the mySql connector itself:
const mysql = require('promise-mysql');
exports.handler = async (query) => {
return mysql.createConnection({
host : '127.0.0.1',
user : 'root',
password : '',
database: 'crm'
}).then(conn =>{
let result = conn.query(query);
conn.end();
return result;
}).then(rows => {
//console.log("These are rows:" + rows);
return rows;
}).catch(error => {
return error;
});
};
The task file can have any number of things it needs to complete, which will be different when I start adding tasks.
I need that job.runJob completes, or that it catches an error, from whatever location it originated, so I can continue with the forEach.
I have tried using map and what not, but the end result is always the same.
What am I doing wrong?
You can use Promise.all method :
const promises = event.Records.map(record => {
const { body: jobName } = record;
const { messageAttributes } = record;
const job = jobLoader.loadJob(jobName);
return job.runJob(messageAttributes).then(res => {
console.log('Show results', res);
}).catch(err => {
record.failed = true;
record.failureMessage = err.message;
console.log('I errored');
throw new Error('Your error !');
});
});
try {
const results = await Promise.all(promises);
console.log('All done');
} catch (e) {
console.log('Something has an error', e);
}
don't forget to make your function async !
I managed to solve it, and still keep details about the execution:
Something like this:
for (let prop in event.Records){
const { body: jobName } = event.Records[prop];
const { messageAttributes } = event.Records[prop];
const job = jobLoader.loadJob(jobName);
await job.runJob(messageAttributes).then(res => {
console.log('Show results', res);
}).catch(err => {
event.Records[prop].failed = true;
event.Records[prop].failed = err.message;
console.log('I errored');
});
}

Resources